1. 程式人生 > >樂觀鎖和悲觀鎖的區別(最全面的分析)

樂觀鎖和悲觀鎖的區別(最全面的分析)

       悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿資料的時候都認為別人會修改,所以每次在拿資料的時候都會上鎖,這樣別人想拿這個資料就會block直到它拿到鎖。傳統的關係型資料庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)。

        樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿資料的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個資料,可以使用版本號等機制。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量,像資料庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。

        兩種鎖各有優缺點,不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況下,即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生衝突,上層應用會不斷的進行retry,這樣反倒是降低了效能,所以這種情況下用悲觀鎖就比較合適。

        本質上,資料庫的樂觀鎖做法和悲觀鎖做法主要就是解決下面假設的場景,避免丟失更新問題:
        一個比較清楚的場景
        下面這個假設的實際場景可以比較清楚的幫助我們理解這個問題:
假設噹噹網上使用者下單買了本書,這時資料庫中有條訂單號為001的訂單,其中有個status欄位是’有效’,表示該訂單是有效的;
後臺管理人員查詢到這條001的訂單,並且看到狀態是有效的
使用者發現下單的時候下錯了,於是撤銷訂單,假設執行這樣一條SQL: update order_table set status = ‘取消’ where order_id = 001;
後臺管理人員由於在b這步看到狀態有效的,這時,雖然使用者在c這步已經撤銷了訂單,可是管理人員並未重新整理介面,看到的訂單狀態還是有效的,於是點選”發貨”按鈕,將該訂單發到物流部門,同時執行類似如下SQL,將訂單狀態改成已發貨:update order_table set status = ‘已發貨’ where order_id = 001

觀點1:只有衝突非常嚴重的系統才需要悲觀鎖;“所有悲觀鎖的做法都適合於狀態被修改的概率比較高的情況,具體是否合適則需要根據實際情況判斷。”,表達的也是這個意思,不過說法不夠準確;的確,之所以用悲觀鎖就是因為兩個使用者更新同一條資料的概率高,也就是衝突比較嚴重的情況下,所以才用悲觀鎖。

觀點2:最後提交前作一次select for update檢查,然後再提交update也是一種樂觀鎖的做法,的確,這符合傳統樂觀鎖的做法,就是到最後再去檢查。但是wiki在解釋悲觀鎖的做法的時候,’It is not appropriate for use in web application development.’, 現在已經很少有悲觀鎖的做法了,所以我自己將這種二次檢查的做法也歸為悲觀鎖的變種,因為這在所有樂觀鎖裡面,做法和悲觀鎖是最接近的,都是先select for update,然後update

在實際應用中我們在更新資料的時候,更嚴謹的做法是帶上更新前的“狀態”,如

update order_table set status = ‘取消’ where order_id = 001 and status = ‘待支付’ and ..........; 

update order_table set status = ‘已發貨’ where order_id = 001 and status = ‘已支付’ and ..........;

然後在業務邏輯程式碼裡判斷更新的記錄數,為0說明資料庫已經被更新了,否則是正常的。