使用FMDB多线程訪问数据库,及database is locked的问题
今天最終攻克了多線程同一時候訪問數據庫時,報數據庫鎖定的問題。錯誤信息是:
Unknown error finalizing or resetting statement (5: database is locked)
最后通過FMDatabaseQueue攻克了這個問題。本文總結一下:
FMDatabase不能多線程使用同一個實例
多線程訪問數據庫,不能使用同一個FMDatabase的實例,否則會發生異常。假設線程使用單獨的FMDatabase實例是同意的,可是相同有可能發生database is locked的問題。
這是因為多線程對sqlite的競爭引起的
我的app一開始就是多線程使用單獨的FMDatabase實例訪問數據庫,盡管沒有引起crash。可是還是出現了database is locked問題,造成非常多數據沒有如預期寫入數據庫
使用FMDatabaseQueue,問題依然
后來上FMDB的官網看了文檔,確認用FMDatabaseQueue能夠解決問題,API也比較簡單:
NSString *dbFilePath = [PathResolver databaseFilePath]; queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath]; [queue inDatabase:^(FMDatabase *db){// access db }];可是實際測試了一下,還是database is locked讀了一下相關的源代碼。FMDatabaseQueue解決問題的思路是:創建一個隊列。然后將放入隊列的block順序運行,這樣避免了多線程同一時候訪問數據庫
而我的代碼是多線程各創建FMDatabaseQueue的實例,所以事實上有多個隊列,因此還是存在數據庫競爭的問題,和用FMDatabase時是一樣的
共享同一個FMDatabaseQueue實例
于是接下來我讓每一個線程使用同一個Queue實例。問題就順利攻克了
實現的方式,一開始我想給FMDatabase添加一個單例方法。可是這樣以后升級FMDB會比較麻煩。所以最后我是創建了一個Helper類
@implementation LosDatabaseHelper{FMDatabaseQueue* queue; }-(id) init {self = [super init];if(self){NSString *dbFilePath = [PathResolver databaseFilePath];queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath];}return self; }+(LosDatabaseHelper*) 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){block(db);}]; }@end系統中其它的類。使用這個Helper類的單例,這樣保證了全局僅僅有唯一的FMDatabaseQueue實例。
注意,由于Helper內部持有的是FMDatabaseQueue,所以能夠這么做。假設包裝的是FMDatabase類。就絕對會有問題。由于FMDatabase實例不能在多線程環境共享
使用FMDatabaseQueue之后。管理db
原本使用FMDatabase類,須要手工調用db的open和close方法
可是用FMDatabaseQueue,不須要調用open。由于查看代碼發現,Queue已經open了。至于要不要close,我也不確定,由于官方的sample code沒有調用close。實際應用中,我也沒有調用。好像沒有問題。假設須要close的話,我想能夠在Helper類的公共方法里添加調用close queue就能夠了。以下是close的源代碼:
- (void)close {FMDBRetain(self);dispatch_sync(_queue, ^() { [_db close];FMDBRelease(_db);_db = 0x00;});FMDBRelease(self); }所以。使用Queue,是不須要自己打開和關閉db的。可是假設使用了FMResultSet,rs倒是須要關閉,否則會報warning: if ([db hasOpenResultSets]) {NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]");
為了不看到warning,我都在block里調用了[rs close]
刷新數據庫文件路徑
詳細到我們的應用,另一個特殊問題須要考慮。
由于我們的APP能夠切換賬戶,而賬戶的db文件是獨立的。所以當用戶又一次登錄的時候,須要刷新一下Helper的queue
+(void) refreshDatabaseFile {LosDatabaseHelper *instance = [self sharedInstance];[instance doRefresh]; }-(void) doRefresh {NSString *dbFilePath = [PathResolver databaseFilePath];queue = [FMDatabaseQueue databaseQueueWithPath:dbFilePath]; }假設不這么做,因為Helper是單例,那么切換賬戶以后。用戶B訪問的還是用戶A的數據庫。刷新的調用。一般放在登錄之后。進入主頁面之前就能夠了
隊列和線程
在debug過程中,順便看到一個現象。盡管多個block都是放到同一個隊列里。可是事實上是跑在不同的thread里
不要混淆隊列和線程的概念。使用GCD時。開發人員關注的是把block放到隊列中,可是同一個隊列事實上能夠相應多個thread,為block分配thread,是GCD框架負責的,開發人員不須要關注。
僅僅要把操作放到合適的隊列里,GCD就會完畢線程的創建,分配與回收
總結
以上是生活随笔為你收集整理的使用FMDB多线程訪问数据库,及database is locked的问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 7.18 Shell 管道 重定向 链接
- 下一篇: N001-SQL Server 2016