FMDB可能是iOS中最常用的數據庫第三方框架. 在項目中, 對于簡單的數據, 我們一般是儲存在偏好設置中, 或者存入XML文件, 需要的時候再讀取. 但是對于大量的數據, 或者是需要后續(xù)頻繁操作的數據, 我們只能把這些數據存入數據庫中.
在最近的一個項目中, 就遇到了需要頻繁操作數據庫內數據的情況, 頻繁的增刪讀寫.隨之而來的就遇到了數據庫鎖死問題. 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做成一個單例, 所有的的數據庫操作在這個線程中串行執(zhí)行. 當需要執(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
之后, 只需要把對數據庫的各種操作都放入- (void)inDatabase:(void(^)(FMDatabase*))block
內執(zhí)行就行了.
由于筆者知識有限,如有錯誤蚣抗,歡迎討論指出卓起。