FMDB可能是iOS中最常用的數(shù)據(jù)庫第三方框架. 在項目中, 對于簡單的數(shù)據(jù), 我們一般是儲存在偏好設(shè)置中, 或者存入XML文件, 需要的時候再讀取. 但是對于大量的數(shù)據(jù), 或者是需要后續(xù)頻繁操作的數(shù)據(jù), 我們只能把這些數(shù)據(jù)存入數(shù)據(jù)庫中.
在最近的一個項目中, 就遇到了需要頻繁操作數(shù)據(jù)庫內(nèi)數(shù)據(jù)的情況, 頻繁的增刪讀寫.隨之而來的就遇到了數(shù)據(jù)庫鎖死問題. FMDB報錯為database locked.
首先先弄懂為什么會出現(xiàn)數(shù)據(jù)庫鎖死問題. 當(dāng)A在寫入數(shù)據(jù)的時候, 會先鎖定數(shù)據(jù)庫再進行寫入操作. 然而這時, B又馬上想寫入數(shù)據(jù), 因為數(shù)據(jù)庫已經(jīng)被A鎖住了, 所以B無法進行寫入操作, 只能先等待A操作完成后再打開數(shù)據(jù)庫. 當(dāng)?shù)却臅r間過長的時候, 數(shù)據(jù)庫就會報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對象. 那我們應(yīng)該怎么處理數(shù)據(jù)庫鎖死問題呢? 官方已經(jīng)給出了答案:
Instead, use
FMDatabaseQueue.
然后, 樓主參考了網(wǎng)絡(luò)各路大神的意見, 一致的解決版本是建立一個DatabaseHelper的類, 用來管理數(shù)據(jù)庫, 應(yīng)該說是管理數(shù)據(jù)庫線程. 即是把FMDatabaseQueue做成一個單例, 所有的的數(shù)據(jù)庫操作在這個線程中串行執(zhí)行. 當(dāng)需要執(zhí)行一個操作時, 先把操作放入串行隊列, 執(zhí)行完前一個操作再執(zhí)行下一個操作.
上代碼:
#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) {
//創(chuàng)建表
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
之后, 只需要把對數(shù)據(jù)庫的各種操作都放入- (void)inDatabase:(void(^)(FMDatabase*))block內(nèi)執(zhí)行就行了.
由于筆者知識有限,如有錯誤,歡迎討論指出。