鎖機制
SQLite基于鎖來實作并發控制。SQLite的鎖是粗粒度的,并不擁有PostgreSQL那樣細粒度的行鎖,這也使得SQLite較為輕量級。當一個連接配接要寫資料庫時,所有其它的連接配接都被鎖住,直到寫連接配接結束它的事務。
SQLite的資料庫連接配接有5種狀态:
狀态 | 對應的鎖 |
---|---|
未加鎖 | — |
共享(shared) | 共享鎖 |
預留(reserved) | 預留鎖 |
未決(pending) | 未決鎖 |
排它(exclusive) | 排它鎖 |
SQL使用鎖逐漸提升機制,上面的表格從上到下,對應鎖的等級逐漸提升,等級越高權限就越大。
未加鎖:
未和資料庫建立連接配接、已建立連接配接但是還沒通路資料庫、已用BEGIN開始了一個事務但未開始讀寫資料庫,處于這些情形時是未加鎖狀态。
共享:
連接配接需要從資料庫中讀取資料時,需要申請獲得一個共享鎖,如果獲得成功,則進入共享狀态。
預留:
連接配接需要寫資料庫時,首先申請一個預留鎖,一個資料庫同時隻能有一個預留鎖,預留鎖可以與共享鎖共存。獲得預留鎖後進入預留狀态,這時會先在緩沖區中進行需要的修改、更新操作,操作後的結果依然儲存在緩沖區中,未真正寫入資料庫。
未決:
連接配接從預留升為排它前,需要先升為未決,這時其它連接配接就不能獲得共享鎖了,但已經擁有共享鎖的連接配接仍然可以繼續正常讀資料庫,此時,擁有未決鎖的連接配接等待其它擁有共享鎖的連接配接完成工作并釋放其共享鎖後,提成到排它鎖。
排它:
連接配接需要送出修改時,需要将預留鎖升為排它鎖,這時其它連接配接都無法獲得任何鎖,直到目前連接配接的排它狀态結束。
一個連接配接讀資料的流程如下:
一個連接配接寫資料的流程如下:
死鎖
在使用事務的情況下,SQLite的鎖機制存在死鎖的可能性。
見下面例子:
執行順序 | 連接配接A | 連接配接B |
---|---|---|
1 | BEGIN; | |
2 | BEGIN; | |
3 | INSERT INTO foo VALUES (‘x’); | |
4 | SELECT * FROM foo; | |
5 | COMMIT; | |
6 | SQL error: database is locked | |
7 | INSERT INTO foo VALUES (‘x’); | |
8 | SQL error: database is locked |
執行順序3:連接配接B要執行寫操作,獲得預留鎖
執行順序4:連接配接A要執行讀操作,獲得共享鎖
執行順序5:連接配接B要送出修改,預留鎖更新為未決鎖
執行順序6:連接配接B想要更新為排它鎖,必須先等待連接配接A釋放共享鎖
執行順序7:連接配接A要執行寫操作,需要獲得預留鎖
執行順序8:連接配接A獲得預留鎖失敗,必須先等待連接配接B釋放未決鎖
于是連接配接A和連接配接B互相等待對方,發生死鎖。
可以通過正确的使用事務類型來解決以上死鎖問題,這裡不再陳述。
WAL技術
SQLite在3.7.0開始引入了WAL技術,全稱叫Write Ahead Log(預寫日志)。
其原理是:修改并不直接寫入到資料庫檔案中,而是寫入到另外一個稱為WAL的檔案中;如果事務失敗,WAL中的記錄會被忽略,撤銷修改;如果事務成功,它将在随後的某個時間被寫回到資料庫檔案中,送出修改。
WAL使用檢查點将修改寫回資料庫,預設情況下,當WAL檔案發現有1000頁修改時,将自動調用檢查點。這個頁數大小可以自行配置。
WAL技術帶來以下優點:
- 讀寫操作不再互相阻塞,一定程度上解決了SQLite在處理高并發上的性能瓶頸
- 大多數場景中,帶來很大的性能提升
- 磁盤I/O行為更容易被預測
WAL也有一些缺點,不過在iOS開發中影響不大,除非資料達到GB級時,性能才會降低。
如何啟用WAL:
如果是直接使用SQLite,需要設定如下編譯訓示:
如果是使用Core Data,則已經預設開啟了WAL模式。
#sqlite 原文位址:http://liuduo.me/2016/04/02/sqlitelockandwal/