iOS 开发者的日常iOS开发iOS Developer

FMDB database locked问题解析

2016-12-30  本文已影响185人  KenZhangCn

FMDB可能是iOS中最常用的数据库第三方框架. 在项目中, 对于简单的数据, 我们一般是储存在偏好设置中, 或者存入XML文件, 需要的时候再读取. 但是对于大量的数据, 或者是需要后续频繁操作的数据, 我们只能把这些数据存入数据库中.

在最近的一个项目中, 就遇到了需要频繁操作数据库内数据的情况, 频繁的增删读写.随之而来的就遇到了数据库锁死问题. FMDB报错为database locked.

首先先弄懂为什么会出现数据库锁死问题. 当A在写入数据的时候, 会先锁定数据库再进行写入操作. 然而这时, B又马上想写入数据, 因为数据库已经被A锁住了, 所以B无法进行写入操作, 只能先等待A操作完成后再打开数据库. 当等待的时间过长的时候, 数据库就会报database locked错误. 这个时间好像是2s.

这里先贴出源码中的一句话 :

Using a single instance of <FMDatabase> from multiple threads at once is a bad idea. It has always been OK to make a <FMDatabase> object per thread. Just don't share a single instance across threads, and definitely not across multiple threads at the same time.

告诉我们别妄想着把database做成单例, 也别想着使用多线程访问database对象. 那我们应该怎么处理数据库锁死问题呢? 官方已经给出了答案:

Instead, use FMDatabaseQueue.

然后, 楼主参考了网络各路大神的意见, 一致的解决版本是建立一个DatabaseHelper的类, 用来管理数据库, 应该说是管理数据库线程. 即是把FMDatabaseQueue做成一个单例, 所有的的数据库操作在这个线程中串行执行. 当需要执行一个操作时, 先把操作放入串行队列, 执行完前一个操作再执行下一个操作.

上代码:

#import "BPBDBHelper.h"

@implementation BPBDBHelper

{
    FMDatabaseQueue* queue;
}

- (id)init {
    self = [super init];
    if(self){
        NSString *dbFilePath = [self getDatabasePath];
        queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
    }
    return self;
}

+ (BPBDBHelper *)sharedInstance {
    static dispatch_once_t pred = 0;
    __strong static id _sharedObject = nil;
    dispatch_once(&pred, ^{
        _sharedObject = [[self alloc] init];
    });
    return _sharedObject;
}

- (void)inDatabase:(void(^)(FMDatabase*))block {
    [queue inDatabase:^(FMDatabase *db){
        if ([db open]) {
            block(db);
        }
        [db close];
    }];
}

- (BOOL)creatDatabase {
    [queue inDatabase:^(FMDatabase *db) {
        //创建表
        if ([db open]) {
            NSString *positionSql = @"CREATE TABLE IF NOT EXISTS 'position' (******)";
            [db executeUpdate:positionSql];
       }
        [db close];
    }];
    return true;
}

- (NSString *)getDatabasePath {
    NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [documentsPath stringByAppendingPathComponent:@"position.sqlite"];
    return filePath;
}

+ (void)refreshDatabaseFile {
    BPBDBHelper *instance = [self sharedInstance];
    [instance doRefresh];
}

- (void)doRefresh {
    NSString *dbFilePath = [self getDatabasePath];
    queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];
}
@end

之后, 只需要把对数据库的各种操作都放入- (void)inDatabase:(void(^)(FMDatabase*))block内执行就行了.


由于笔者知识有限,如有错误,欢迎讨论指出。

上一篇下一篇

猜你喜欢

热点阅读