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