SQLite鎖機制

zcbin發表於2018-03-04

SQLite有一個加鎖表,用來幫助不同的寫資料庫都能夠在最後一刻加鎖,保證最大的併發性。 SQLite有五種不同的鎖狀態:

SQLite鎖機制

  • unlocked:最初狀態,未加鎖,連線還沒有開始訪問資料庫
  • shared:共享鎖,多個連線可以同時獲得並保持shared鎖,即多個連線可以同時從同一個資料庫中讀資料。
  • reserved:保留鎖,一個連線如果想要寫資料庫,必須首先獲得一個reserved鎖。一個資料庫同時只能有一個reserved鎖,該reserved鎖可以與shared鎖共存,它是寫資料庫的第一個階段。reserved鎖既不阻止其他擁有shared鎖的連線繼續讀資料庫,也不阻止其他連線獲得新的shared鎖。當一個連線獲得了reserved鎖,就可以開始處理資料庫修改操作了。儘管這些修改只能在緩衝區進行,而不是實際寫到磁碟,修改儲存在記憶體緩衝區中。
  • pending:未決鎖,當連線想要提交修改時,需要將reserved鎖提升為pending鎖。獲得pending鎖後,其他連線就不能再獲得新的共享鎖了,但已經擁有shared鎖的連線仍然可以繼續正常讀資料庫。此時,擁有reserved鎖的連線等待其他擁有shared鎖的連線完成工作並釋放其共享鎖。
  • exclusive:排它鎖,一旦所有的其他共享鎖都被釋放,擁有pending鎖的連線就可以將鎖提升至exclusive鎖,此時就可以自由地對資料庫進行修改。所有以前所快取的修改都會被寫到資料庫檔案中。

SQLite事務

事務定義了一組SQL命令的邊界,這組命令或者作為一個整體被全部執行,或者都不執行,這被稱為資料庫完整性的原子性原則。 預設情況下,SQLite中每條SQL語句自成事務(自動提交模式)。所有成功完成的命令偶都自動提交,所有遇見錯誤的命令都回滾。

事務屬性:

  • 原子性:保證任務中的所有操作都執行完畢;否則,事務會在出現錯誤時終止,並回滾之前所有操作到原始狀態。
  • 一致性:如果事務成功執行,則資料庫的狀態得到了進行了正確的轉變。
  • 隔離性:保證不同的事務相互獨立、透明地執行。
  • 永續性:即使出現系統故障,之前成功執行的事務的結果也會持久存在。

預設情況下,SQLite的每條語句就是一條事務,比如執行一條INSERT語句,執行後,INSERT語句就生效,提交到了資料庫中。

SQLite使用以下命令控制事務:

  • BEGIN TRNSACTION --開始事務
  • COMMIT --提交,也可以使用END TRANSACTION命令
  • ROLLBACK --回滾

SQLite事務型別

SQLite有三種不同的事務型別:

  • DEFERRED(推遲)
  • MMEDIATE(立即)
  • EXCLUSIVE(排它)

事務型別在BEGIN命令中指定。

SQLite鎖機制

一個deferred事務不獲取任何鎖,直到它需要鎖的時候。而且BEGIN語句本身也不會做什麼事情——它開始於UNLOCK狀態;預設情況下是這樣的。如果僅僅用BEGIN開始一個事務,那麼事務就是DEFERRED的,同時它不會獲取任何鎖,當對資料庫進行第一次讀操作時,它會獲取SHARED LOCK;同樣,當進行第一次寫操作時,它會獲取RESERVED LOCK。

由BEGIN開始的Immediate事務會試著獲取RESERVED LOCK。如果成功,BEGIN IMMEDIATE保證沒有別的連線可以寫資料庫。但是,別的連線可以對資料庫進行讀操作,但是RESERVED LOCK會阻止其它的連線BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,SQLite會返回SQLITE_BUSY錯誤。這時你就可以對資料庫進行修改操作,但是你不能提交,當你COMMIT時,會返回SQLITE_BUSY錯誤,這意味著還有其它的讀事務沒有完成,得等它們執行完後才能提交事務。

Exclusive事務會試著獲取對資料庫的EXCLUSIVE鎖。這與IMMEDIATE類似,但是一旦成功,EXCLUSIVE事務保證沒有其它的連線,所以就可對資料庫進行讀寫操作了。 上面那個例子的問題在於兩個連線最終都想寫資料庫,但是他們都沒有放棄各自原來的鎖,最終,shared 鎖導致了問題的出現。如果兩個連線都以BEGIN IMMEDIATE開始事務,那麼死鎖就不會發生。在這種情況下,在同一時刻只能有一個連線進入BEGIN IMMEDIATE,其它的連線就得等待。BEGIN IMMEDIATE和BEGIN EXCLUSIVE通常被寫事務使用。就像同步機制一樣,它防止了死鎖的產生。 基本的準則是:如果你在使用的資料庫沒有其它的連線,用BEGIN就足夠了。但是,如果你使用的資料庫在其它的連線也要對資料庫進行寫操作,就得使用BEGIN IMMEDIATE或BEGIN EXCLUSIVE開始你的事務

SQLite死鎖

SQLite可能會發生死鎖。如:

SQLite鎖機制

B連線執行INSERT語句時獲得了reserved鎖,reserved鎖只有一個,而且它不會阻止其他連線獲取shared鎖;A連線執行SELECT語句獲得shared鎖;此時B連線進行commit,由於shared鎖還未釋放,B連線無法獲得pending鎖;A連線執行INSERT語句,想獲取reserved鎖,但是reserved鎖只有一個且被B連線持有,所以A連線等待reserved鎖;A連線shared鎖一直沒有釋放,B連線就不能提升到exclusive鎖,等待A連線釋放shared鎖。

使用正確的事務型別可以避免死鎖。

一個DEFERRED事務不獲取任何鎖(直到它需要鎖的時候),BEGIN語句本身也不會做什麼事情——它開始於UNLOCK狀態。預設情況下就 是這樣的,如果僅僅用BEGIN開始一個事務,那麼事務就是DEFERRED的,同時它不會獲取任何鎖;當對資料庫進行第一次讀操作時,它會獲取 SHARED鎖;同樣,當進行第一次寫操作時,它會獲取RESERVED鎖。

由BEGIN開始的IMMEDIATE事務會嘗試獲取RESERVED鎖。如果成功,BEGIN IMMEDIATE保證沒有別的連線可以寫資料庫。但是,別的連線可以對資料庫進行讀操作;但是,RESERVED鎖會阻止其它連線的BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,當其它連線執行上述命令時,會返回SQLITE_BUSY 錯誤。這時你就可以對資料庫進行修改操作了,但是你還不能提交,當你 COMMIT時,會返回SQLITE_BUSY錯誤,這意味著還有其它的讀事務沒有完成,得等它們執行完後才能提交事務。

EXCLUSIVE事務會試著獲取對資料庫的EXCLUSIVE鎖。這與IMMEDIATE類似,但是一旦成功,EXCLUSIVE事務保證沒有其它的連線,所以就可對資料庫進行讀寫操作了。

相關文章