SQLite也可能出現死鎖


提到鎖就不得不說到死鎖的問題,而SQLite也可能出現死鎖。
下面舉個例子:
連接1:BEGIN (UNLOCKED)
連接1:SELECT ... (SHARED)
連接1:INSERT ... (RESERVED)
連接2:BEGIN (UNLOCKED)
連接2:SELECT ... (SHARED)
連接1:COMMIT (PENDING,嘗試獲取EXCLUSIVE鎖,但還有SHARED鎖未釋放,返回SQLITE_BUSY)
連接2:INSERT ... (嘗試獲取RESERVED鎖,但已有PENDING鎖未釋放,返回SQLITE_BUSY)
現在2個連接都在等待對方釋放鎖,於是就死鎖了。當然,實際情況並沒那么糟糕,任何一方選擇不繼續等待,回滾事務就行了。

不過要更好地解決這個問題,就必須更深入地了解事務了。
實際上BEGIN語句可以有3種起始狀態:
DEFERRED:默認值,開始事務時不獲取任何鎖。進行第一次讀操作時獲取SHARED鎖,進行第一次寫操作時獲取RESERVED鎖。
IMMEDIATE:開始事務時獲取RESERVED鎖。
EXCLUSIVE:開始事務時獲取EXCLUSIVE鎖。

現在考慮2個事務在開始時都使用IMMEDIATE方式:
連接1:BEGIN IMMEDIATE (RESERVED)
連接1:SELECT ... (RESERVED)
連接1:INSERT ... (RESERVED)
連接2:BEGIN IMMEDIATE (嘗試獲取RESERVED鎖,但已有RESERVED鎖未釋放,因此事務開始失敗,返回SQLITE_BUSY,等待用戶重試)
連接1:COMMIT (EXCLUSIVE,寫入完成后釋放)
連接2:BEGIN IMMEDIATE (RESERVED)
連接2:SELECT ... (RESERVED)
連接2:INSERT ... (RESERVED)
連接2:COMMIT (EXCLUSIVE,寫入完成后釋放)
這樣死鎖就被避免了。

而EXCLUSIVE方式則更為嚴苛,即使其他連接以DEFERRED方式開啟事務也不會死鎖:
連接1:BEGIN EXCLUSIVE (EXCLUSIVE)
連接1:SELECT ... (EXCLUSIVE)
連接1:INSERT ... (EXCLUSIVE)
連接2:BEGIN (UNLOCKED)
連接2:SELECT ... (嘗試獲取SHARED鎖,但已有EXCLUSIVE鎖未釋放,返回SQLITE_BUSY,等待用戶重試)
連接1:COMMIT (EXCLUSIVE,寫入完成后釋放)
連接2:SELECT ... (SHARED)
連接2:INSERT ... (RESERVED)
連接2:COMMIT (EXCLUSIVE,寫入完成后釋放)
不過在並發很高的情況下,直接獲取EXCLUSIVE鎖的難度比較大;而且為了避免EXCLUSIVE狀態長期阻塞其他請求,最好的方式還是讓所有寫事務都以IMMEDIATE方式開始。
順帶一提,要實現重試的話,可以使用sqlite3_busy_timeout()或sqlite3_busy_handler()函數。

由此可見,要想保證線程安全的話,可以有這4種方式:

  • SQLite使用單線程模式,用一個專門的線程訪問數據庫。
  • SQLite使用單線程模式,用一個線程隊列來訪問數據庫,隊列一次只允許一個線程執行,隊列里的線程共用一個數據庫連接。
  • SQLite使用多線程模式,每個線程創建自己的數據庫連接。
  • SQLite使用串行模式,所有線程共用全局的數據庫連接。


作者:Crazy2015
鏈接:https://www.jianshu.com/p/15051fbd5a35
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM