1. 程式人生 > >樂觀鎖與悲觀鎖的適用場景

樂觀鎖與悲觀鎖的適用場景

MVCC是每個接觸資料庫尤其是分散式網際網路應用的開發人員應知應會的內容,而架構師更應該知道如何在悲觀鎖和樂觀鎖之間進行平衡與選擇,這裡不做展開,只想補充以下內容,來自於之前和現在的專案經驗:

樂觀鎖在web應用還有一種應用場景就是在前端頁面事務無法控制到的位置通過version檢查避免髒資料的覆蓋操作。比如在悲觀鎖環境下,當多個使用者在各自的瀏覽器上修改同一份資料的不同域時,由於事務延伸不到客戶的瀏覽器上,因此當他們提交時,伺服器、資料庫會認為是多份獨立的事務提交,將相繼全部成功,最終導致最後提交的有效,前幾次提交的資料都被最後一次提交的資料覆蓋,形象一點:

資料庫原始資料


+----+------+--------+-----+--------+

| id | name | gender | age | height |

+----+------+--------+-----+--------+

| 1  | Jay  | male   | 29  | 1.88   |

+----+------+--------+-----+--------+


此時三個使用者都打開了“編輯使用者資訊”頁面,拿到了相同的資料,但是

- 甲將性別改成“unknown”
- 乙將年齡改成28
- 丙將身高改成1.85
假設他們依次提交,最終的結果將是


+----+------+--------+-----+--------+

| id | name | gender | age | height |

+----+------+--------+-----+--------+

| 1  | Jay  | male   | 29  | 1.85   |

+----+------+--------+-----+--------+


丙的提交生效,其他人的修改被髒資料覆蓋

如果使用樂觀鎖version機制,情況會有很大不同

資料庫原始資料


+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | male   | 29  | 1.88   | 1       |

+----+------+--------+-----+--------+---------+


還是按照上面的場景修改資料,還是按照上面的順序提交,伺服器會將version欄位返回至客戶端,客戶端提交時也會帶上version資訊:

- 甲提交,資料更新為


+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | unknown| 29  | 1.88   | 2       |

+----+------+--------+-----+--------+---------+


-
乙提交,由於version已經更新為2,資料庫認為有衝突(其實MVCC的version和傳統的VCS是一樣的,會有衝突發生),更新失敗,拋異常,伺服器提示“資料已被更新,請重新整理後重試”,乙重新整理獲得甲更新後的資料,修改年齡為28,提交,資料更新為


+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | unknown| 28  | 1.88   | 3       |

+----+------+--------+-----+--------+---------+


- 丙提交,和乙一樣,被告知“資料已被更新,請重新整理後重試”,重新整理,更新,提交,資料更新為


+----+------+--------+-----+--------+---------+

| id | name | gender | age | height | version |

+----+------+--------+-----+--------+---------+

| 1  | Jay  | unknown| 28  | 1.85   | 4       |

+----+------+--------+-----+--------+---------+


這應該是大家所預期的結果。而如果使用悲觀鎖要解決這個問題,只能在伺服器端做額外的處理,辨識此次更新的欄位,然後更新前查詢一次資料庫,獲得最新的資料,僅更新發生變化的欄位,然後提交。或者,頁面就應該避免多欄位大表單的提交,把每次可更新的內容進行拆分,比如現在幾乎所有的SNS的使用者資訊更新頁面都會按“基本資訊”、“學校資訊”、“就業資訊”等分段儲存(當然這麼做的原因有很多,比如使用者體驗,對於一個長長的大表格,使用者更能接受“少量多次”的提交方式,而且如果在儲存前出現瀏覽器崩潰、宕機等意外情況,未儲存而需要重填的資料量不會很大。髒資料覆蓋問題只是其中一個)

最後補充兩點實戰經驗

- 如果需要在Hibernate中使用MVCC,直接在entity中定義一個int型別的欄位,然後使用@Version修飾該欄位
-
在真實環境中,若使用MVCC並且允許使用者重複更新,每次頁面提交後,應該將資料庫最新的version值傳回客戶端。如果使用REST,直接放在response的header裡是一種可行的做法