天天看點

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

新浪微網誌

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

Unknown error finalizing or resetting statement (5: database is locked)

最後通過FMDatabaseQueue解決了這個問題,本文總結一下:

多線程通路資料庫,不能使用同一個FMDatabase的執行個體,否則會發生異常。如果線程使用單獨的FMDatabase執行個體是允許的,但是同樣有可能發生database is locked的問題。這是由于多線程對sqlite的競争引起的

我的app一開始就是多線程使用單獨的FMDatabase執行個體通路資料庫,雖然沒有引起crash,但是還是出現了database is locked問題,造成很多資料沒有如預期寫入資料庫

後來上FMDB的官網看了文檔,确認用FMDatabaseQueue可以解決這個問題,API也比較簡單:

但是實際測試了一下,還是database is locked

讀了一下相關的源碼,FMDatabaseQueue解決這個問題的思路是:建立一個隊列,然後将放入隊列的block順序執行,這樣避免了多線程同時通路資料庫

而我的代碼是多線程各建立FMDatabaseQueue的執行個體,是以其實有多個隊列,是以還是存在資料庫競争的問題,和用FMDatabase時是一樣的

于是接下來我讓每個線程使用同一個Queue執行個體,問題就順利解決了

實作的方式,一開始我想給FMDatabase增加一個單例方法,但是這樣以後更新FMDB會比較麻煩,是以最後我是建立了一個Helper類

系統中其他的類,使用這個Helper類的單例,這樣保證了全局隻有唯一的FMDatabaseQueue執行個體。注意,因為Helper内部持有的是 FMDatabaseQueue,是以可以這麼做,如果包裝的是FMDatabase類,就絕對會有問題。因為FMDatabase執行個體不能在多線程環境 共享

原本使用FMDatabase類,需要手工調用db的open和close方法

但是用FMDatabaseQueue,不需要調用open,因為檢視代碼發現,Queue已經open了。至于要不要close,我也不确定,因 為官方的sample code沒有調用close。實際應用中,我也沒有調用,好像沒有問題。如果需要close的話,我想可以在Helper類的公共方法裡增加調用 close queue就可以了。下面是close的源碼:

是以,使用Queue,是不需要自己打開和關閉db的。但是如果使用了FMResultSet,rs倒是需要關閉,否則會報warning:

為了不看到warning,我都在block裡調用了[rs close]

具體到我們的應用,還有一個特殊問題需要考慮。因為我們的APP可以切換賬戶,而賬戶的db檔案是獨立的。是以當使用者重新登入的時候,需要重新整理一下Helper的queue

如果不這麼做,由于Helper是單例,那麼切換賬戶以後,使用者B通路的還是使用者A的資料庫。重新整理的調用,一般放在登入之後,進入首頁面之前就可以了

在debug過程中,順便看到一個現象。雖然多個block都是放到同一個隊列裡,但是其實是跑在不同的thread裡

不要混淆隊列和線程的概念,使用GCD時,開發者關注的是把block放到隊列中,但是同一個隊列其實可以對應多個thread,為block配置設定thread,是GCD架構負責的,開發者不需要關注。隻要把操作放到合适的隊列裡,GCD就會完成線程的建立,配置設定與回收