詳解悲觀鎖和樂觀鎖

背景mysql

考慮下面兩個併發帶來的問題:sql

一、丟失更新:一個事務的更新結果覆蓋了其它事務的更新結果,即所謂的更新丟失。數據庫

二、髒讀:當一個事務讀取其它完成一半事務的記錄時,就會發生髒讀取。bash

例如:併發

兩個用戶同時修改商品庫存表,A、B同時進入,看到的庫存都是100,A購買一件把庫存修改成99(100-1)。此時B購買兩件把庫存修改成98(100-2),由於A、B同時讀到的庫存都是100,B並不能看到A作的庫存更新,因此形成B髒讀,形成A丟失更新。性能

因此爲了解決這些併發帶來的問題。 咱們須要引入併發控制機制--鎖。ui

鎖分類spa

悲觀鎖3d

悲觀鎖就是用戶修改數據時看起來很悲觀,保守態度,擔憂別的用戶會同時修改這條數據,因此每次修改時會提早把這條數據鎖定起來,只有本身可修改(但別的用戶能夠讀),等本身修改完了再釋放鎖。code

樂觀鎖

樂觀鎖就是用戶修改數據時心態很樂觀,無論別人修改不修改數據,我都不上鎖,我修改的時候判斷下數據有沒有發生變化,沒發生變化我就會更新成功,發生變化了就不會更新成功我再去重試以前的動做直到更新成功。

鎖應用

悲觀鎖

使用悲觀鎖的時候咱們首先必須關閉mysql數據庫的自動提交屬性,由於MySQL默認使用autocommit模式,也就是說,當你執行一個更新操做後,MySQL會馬上將結果進行提交。

關閉命令爲:set autocommit=0;

悲觀鎖通常使用select…for update實現,在執行的時候會鎖定數據,雖然會鎖定數據,可是不影響其餘事務的普通查詢使用。

在咱們使用悲觀鎖的時候事務中的語句例如:

//開始事務

begin;/begin work;/start transaction; (三選一)

//查詢信息

select * from order where id=1 for update;

//修改信息

update order set name='names';

//提交事務

commit;/commit work;(二選一)
複製代碼

此處的查詢語句for update關鍵字,在事務中只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一條數據時會等待其它事務結束後才執行,通常的SELECT查詢則不受影響。

注意事項

執行事務時關鍵字select…for update會鎖定數據,防止其餘事務更改數據。可是鎖定數據也是有規則的。

查詢條件與鎖定範圍:

  • 一、具體的主鍵值爲查詢條件

好比查詢條件爲主鍵ID=1等等,若是此條數據存在,則鎖定當前行數據,若是不存在,則不鎖定。

  • 二、不具體的主鍵值爲查詢條件

好比查詢條件爲主鍵ID>1等等,此時會鎖定整張數據表。

  • 三、查詢條件中無主鍵

會鎖定整張數據表。

  • 四、若是查詢條件中使用了索引爲查詢條件

明確指定索引而且查到,則鎖定整條數據。若是找不到指定索引數據,則不加鎖。

樂觀鎖

  • 一、使用自增加的整數表示數據版本號,更新時檢查版本號是否一致,好比數據庫中數據版本爲666,更新提交時version=666+1,使用該version值(=667)與數據庫version+1(=667)做比較,若是相等,則能夠更新,若是不等則有可能其餘程序已更新該記錄,因此返回錯誤或者發起重試動做。

例如表

student(id,name,version)

1     a       1
複製代碼

當事務一進行更新操做:

update student set name='txt' where id = #{id} and version = #{version};
複製代碼

此時操做完後數據會變爲id = 1,name = txt,version = 2,當另一個事務二一樣執行更新操做的時候,卻發現version != 1,此時事務二就會操做失敗,從而保證了數據的正確性。

  • 二、使用時間戳來實現,原理同上。

  • 三、使用其餘數據庫字段,如:金額,更新時添加條件判斷金額是否變化,原理同上。

樂觀鎖圖示

結論

兩種鎖各有優缺點,不能單純的定義哪一個好於哪一個。樂觀鎖比較適合數據修改比較少,讀取比較頻繁的場景,即便出現了少許的衝突,這樣也省去了大量的鎖的開銷,故而提升了系統的吞吐量。可是若是常常發生衝突(寫數據比較多的狀況下),上層應用不不斷的retry,這樣反而下降了性能,對於這種狀況使用悲觀鎖就更合適。

相關文章
相關標籤/搜索