悲觀鎖與樂觀鎖(資料庫交易)

之前遇上的問題,劃個重點留個整理記錄。

資料庫交易特性ACID中的交易隔離(Isolation)通常利用「鎖(locking)」來實現,目的為避免同一筆資料在併行交易(concurrent transaction)中被不同的交易同時修改以確保資料的完整性。
交易鎖又分為「悲觀鎖(pessimistic locking)」與「樂觀鎖(optimistic locking)」。

常用鎖處理的例子: 電商商品庫存、銀行轉帳業務

##悲觀鎖(Pessimistic Locking)
時機: 悲觀並行控制主要用於資料爭用激烈的環境或發生並發衝突時,”使用鎖保護資料的成本”要低於”回復交易的成本”的環境中。

  • 悲觀並行控制實際上是「先取鎖再訪問」的保守策略,為資料處理的安全提供了保證。
  • 在效率方面,處理加鎖的機制會讓資料庫產生額外的開銷,還有增加產生”死鎖”的機會
  • 在只讀型交易處理中由於不會產生衝突,就沒必要使用鎖,這樣做只能增加系統負載,還有會降低了並行性。
  • 一個交易如果鎖定了某行資料,其他交易就必須等待該交易處理完才可以處理那行資料。(當下資料有對其他執行存取有排他性)
    典型的使用: synchronized (java)

##樂觀鎖(Optimistic Locking)
時機: 樂觀並行控制多數用於”資料爭用不大”、”衝突較少的環境”中,”偶爾回復交易的成本”會低於”讀取資料時鎖定資料的成本”,因此可以獲得”比其他並行控制方法更高的吞吐量”。

  • 假設”多使用者並行的交易在處理時不會彼此互相影響”,”各交易能夠在不產生鎖”的情況下”處理各自影響的那部分資料”。在提交資料更新之前,每個交易會”先檢查在該交易讀取資料後,有沒有其他交易又修改了該資料”。如果其他交易有更新的話,正在提交的交易會進行rollback。

缺點: 樂觀並行控制相信交易之間的資料競爭(data race)的概率是比較小的,因此儘可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死結。但如果直接簡單這麽做,還是有可能會遇到不可預期的結果,例如兩個交易都讀取了資料庫的某一行,經過修改以後寫回資料庫,這時就遇到了問題。(ABA问题)

  • 需要添加重試機制,舉個例子: 資料異動時衝突(JPA中的@version),重新在執行失敗的動作

樂觀並行控制的交易包括以下階段:

  • 讀取:交易將資料讀入快取,這時系統會給交易分派一個時間戳。
  • 校驗:交易執行完畢後,進行提交。這時同步校驗所有交易,如果交易所讀取的資料在讀取之後又被其他交易修改,則產生衝突,交易被中斷回復。
  • 寫入:通過校驗階段後,將更新的資料寫入資料庫。
    典型的使用: Compare and Swap (CAS)、 AtomicReference class、 @Version(Java JPA中的@version 版本控制處理方式)

##總結
樂觀鎖適用於讀大於寫、衝突少的的場景(省去了鎖的開銷,加大了系統的整個吞吐量。如果經常產生衝突,上層應用會不斷的進行retry可能須考慮是否用悲觀鎖)
悲觀鎖寫大於讀、衝突大的的場景(併發量不大且不允許髒讀適合)

more info:資料參考
more info:Optimistic Concurrency wiki
more info:Pessimistic Concurrency wiki