使用FMDB多線程訪問資料庫,及database is locked的問題

來源:互聯網
上載者:User

標籤:fmdb   database is locked   fmdatabasequeue   單例   

今天終於解決了多線程同時訪問資料庫時,報資料庫鎖定的問題,錯誤資訊是:

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順序執行,這樣避免了多線程同時訪問資料庫

而My Code是多線程各建立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就會完成線程的建立,分配與回收


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.