簡述
Sqlite Database是一個比較穩定輕量的小型資料庫,不像是mysql、oracle等資料庫一樣具有單獨的服務程式。基於sqlite的讀寫都是讀寫原始的磁碟檔案,也就是說sqlite的操作完全是磁碟的IO操作。SQLite操作存在一定的異常情況,在客戶端也難以監控,在使用者基數比較大的情況下,往往對極少數的使用者的影響也是致命的。瞭解sqlite便於我們規範的去使用它。
通常的異常情況都包含在以下情況:
- 檔案錯寫
- 檔案鎖 bug
- 檔案 sync 失敗
- 裝置損壞
- 記憶體覆蓋
- 作業系統 bug
- SQLite bug
官方的文件也有介紹到
How To Corrupt An SQLite Database File
鑑於作業系統和Sqlite bug等不可控方面我們更多需要做到能準確的監控,如記憶體不足,類似於微信的做法就是監控到記憶體不足,彈出提示框告知使用者去清理記憶體。下面就來探討下由於操作不當引起的異常。
SQLiteDatabaseLockedException
android.database.sqlite.SQLiteDatabaseLockedException DB引擎在執行job的時候發現獲取不到資料庫的鎖就會丟擲這個異常。資料庫鎖用於防止多執行緒同時寫入資料從而導致DB損壞。所以一般發生在多執行緒的事務操作上。針對同一個事務程式碼段,如果一個事務尚未結束提交,另外一個執行緒便開始介入執行就會導致衝突丟擲此異常。
所以出現上述異常,請檢查你的程式碼。建議的操作是:
- 只使用一個SQLOpenHelper來訪問和操作database,單例你的SQLOpenHelper,避免多個OpenHelper多次open db並寫入資料。
- 當不再需要繼續操作db的時候,關閉所有database helper例項
- 做事務操作的時候一定要保證事務完成呼叫了endTransaction() 或者transaction Successful相關方法。下一個事務操作會在entransaction結束後請求鎖。
檔案鎖
系統檔案鎖問題SQLite依賴於底層的檔案系統對檔案鎖的實現。SQLite 預設鎖是協同鎖。假設有A、B執行緒同時訪問資料庫並寫入資料。這個時候來個C執行緒不是使用SQLite API的方式來運算元據的話(如直接的IO操作),那麼資料庫鎖就會被自動取消,A、B執行緒在沒有鎖保護的狀態同時操作db就可能導致DB的損壞。
建議:
- 儘量使用SQLite API來運算元據庫的讀寫。
- 如果有IO執行緒直接運算元據庫,程式碼邏輯上保證執行緒間的同步順序。
檔案sync失敗
批量寫入比較大的資料我們可能想要更快的速度,網上很多會建議你設定PRAGMA synchronous=OFF, Android對應的設定方式就是:mDatabase.execSQL("PRAGMA synchronous=OFF"); 等同於在說:我期待更快的寫入速度,磁碟驅動器為了達到目標,就耍了個小聰明,忽略開始SQLite的同步操作,先通知到系統我寫入完了。實際上不一定資料完全寫入了,假如這個時候出現了斷電等意外,就會導致真實資料寫入失敗從而引發DB損壞。
建議:
儘量不要設定PRAGMA synchronous=OFF 資料比較大的寫入等操作從sql語句優化和效能的優化入手。
多db操作
很多較為複雜的應用可能一個應用程式不止有一個資料庫。這裡列舉一個場景,應用包含兩個DB。 DatabaseA:預置資料的資料庫,已經設計好表並提前插入了資料。不需要跟隨應用動態的去建立DB。使用者第一次安裝的時候把準備好的DB通過IO的方式拷貝到應用包路徑下面的databases路徑提供給應用訪問,此DB一般只用讀,很少或者完全不寫入資料,是靜態的。 DatabaseB: 跟隨應用的安裝和升級動態的建立和升級,作為應用資料的持久化方式之一。讀寫操作均比較頻繁。
DababaseA會在應用的啟動的時候IO遷移拷貝。DatabaseB動態Create後到處可能都有使用到(包括啟動)
Danger 1:檔案覆蓋
DatabaseB的DBOpenHelperB正在讀寫DatabaseA的表,同時DatabaseA正在被IO執行緒拷貝寫入檔案覆蓋。DBOpenHelperB建立連線可能依然是舊的資料庫,讀寫自然也是到舊的資料庫。部分檔案系統甚至引發IO拷貝被中斷。
Danger 2:交叉讀寫
一般有多個DB會對應多個DBOpenHelper進行讀寫,如過DBOpenHelper做了跨DB的讀寫,就會導致重複的DB Open或Close,影響到了其他DBOpenHelper的正常讀寫。直接的導致鎖異常或者DB損壞等問題。
多DB建議:
- 非SQLite API執行緒IO資料庫請保持IO完全結束後再建立與資料庫的讀寫連線或讀寫操作 管理好你的DBHelper
- DBHelper避免跨DB的操作,一個DB對應一個DBHelper例項
綜上多數情況都是程式碼上操作不當引發的問題,瞭解好原理才能對症下藥,避開危險區。
補充: 減少多程式或多執行緒操作,儘可能單執行緒寫; 減少事務操作,減小事務複雜度。