數據庫中的悲觀鎖和樂觀鎖

如今咱們簡單聊一下數據庫中的悲觀鎖和樂觀鎖。數據庫

悲觀鎖

悲觀鎖正如其名稱,比較悲觀。總會認爲:每當修改數據時,會有其餘線程也會同時修改該數據。因此針對這種狀況悲觀鎖的作法是:讀取數據以後就加鎖 (eg: select...for update),這樣別的線程讀取該數據的時候就須要等待當前線程釋放鎖,得到到鎖的線程才能得到該數據的讀寫權限。從而保證了併發修改數據錯誤的問題。可是因爲阻塞緣由,因此致使吞吐量不高。 悲觀鎖更適用於多寫少讀的狀況。

在這裏插入圖片描述

場景: 同窗A和同窗B都要給你轉500塊錢(開心壞了吧,這樣最終你能獲得1000塊錢)。

使用悲觀鎖的流程:
併發

  1. 同窗A獲取到你的帳戶餘額balance = 0並對該條記錄加鎖。
  2. 同窗B獲取你的帳戶餘額。因爲同窗A已經對這條記錄加鎖了,因此同窗B須要等同窗A轉賬完成(釋放鎖)才能得到餘額。
  3. 同窗A轉帳完成並釋放鎖,此時你的帳戶餘額balance=balance + 500 = 500
  4. 同窗B獲取到你的帳戶餘額balance = 500,並對該條記錄加鎖(若是你人緣好,此時同窗C給你轉帳也是須要等待同窗B轉帳完成才能夠轉帳哦)
  5. 同窗B轉帳完成並釋放鎖(若是有同窗C想給你轉帳,此時同窗C就能夠得到鎖並轉帳了)。此時你的帳戶餘額爲balance = balance + 500 = 1000
  6. 最終你開開心心的獲得了1000塊錢。

假設轉帳過程沒有鎖,咱們看看會發生什麼:
高併發

  1. 同窗A獲取到你的帳戶餘額balance_a = 0(沒有加鎖,此時同窗B也能夠獲取到帳戶餘額)
  2. 同窗B獲取到你的帳戶餘額balance_b = 0
  3. 同窗A轉帳完成,此時你的帳戶餘額爲balance = balance_a + 500 = 500
  4. 同窗B轉帳完成,此時你的帳戶餘額爲balance = balance_b + 500 = 500
  5. 最終同窗A和同窗B都轉了500,可是你最終只得到了500。這必定是不能接受的吧。

在這裏插入圖片描述

丟失的500塊去哪裏了呢?從第2步能夠看到同窗B獲取到的帳戶餘額是0,而不是同窗A轉賬以後的餘額500。因此問題出在這裏,這是高併發場景的常見問題。因此加鎖是很是必須的。可是加了悲觀鎖,同窗都要排隊給我轉帳,對於沒有耐心的同窗就直接不轉賬了,我豈不是錯失了發財的好機會。那有什麼好辦法呢?答案就是下面的樂觀鎖spa

樂觀鎖

樂觀鎖顧名思義比較樂觀,他只有在 更新數據的時候纔會檢查這條數據是否被其餘線程更新了(這點與悲觀鎖同樣,悲觀鎖是在讀取數據的時候就加鎖了)。若是更新數據時,發現這條數據被其餘線程更新了,則這次更新失敗。若是數據未被其餘線程更新,則更新成功。因爲 樂觀鎖沒有了鎖等待,提升了吞吐量,因此樂觀鎖適合多讀少寫的場景。

常見的樂觀鎖實現方式是:版本號version和CAS(compare and swap)。此處只介紹版本號方式。線程

要採用版本號,首先須要在數據庫表中新增一個字段version,表示此條記錄的更新版本,記錄每變更一次,版本號加1。依舊使用上面轉帳的例子說明:
3d

  1. 同窗A獲取到你的帳戶餘額balance = 0和版本號version_a = 0
  2. 同窗B獲取到你的帳戶餘額balance = 0和版本號version_b = 0
  3. 同窗A轉帳完成update table set balance = ${balance}, version = version + 1 and version = 0。(此時版本號爲0,因此更新成功)
  4. 同窗B轉帳完成update table set balance = ${balance}, version = version + 1 and version = 0。(此時版本號爲1,因此更新失敗,更新失敗以後同窗B再轉一次便可)
  5. 同窗B從新轉賬以後,你仍是美滋滋的得到了1000。

總結

悲觀鎖:讀取時加鎖,更新完釋放鎖,再此過程當中會形成其餘線程阻塞,致使吞吐量低,適用於多寫場景。code

樂觀鎖:不加鎖,只有在更新時驗證數據是否被其餘線程更新,吞吐量較高,適用於多讀場景。blog

相關文章
相關標籤/搜索