天天看點

并發控制--悲觀鎖和樂觀鎖詳解

背景

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

1、

丢失更新

:一個事務的更新結果覆寫了其它事務的更新結果,即所謂的更新丢失。

2、

髒讀

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

例如:

兩個使用者同時修改商品庫存表,A、B同時進入,看到的庫存都是100,A購買一件把庫存修改為99(100-1)。此時B購買兩件把庫存修改為98(100-2),因為A、B同時讀到的庫存都是100,B并不能看到A做的庫存更新,是以造成B髒讀,造成A丢失更新。

是以為了解決這些并發帶來的問題。 我們需要引入并發控制機制--鎖。

鎖分類 悲觀鎖

悲觀鎖就是使用者修改資料時看起來很悲觀,保守态度,擔心别的使用者會同時修改這條資料,是以每次修改時會提前把這條資料鎖定起來,隻有自己可修改(但别的使用者可以讀),等自己修改完了再釋放鎖。

樂觀鎖

樂觀鎖就是使用者修改資料時心态很樂觀,不管别人修改不修改資料,我都不上鎖,我修改的時候判斷下資料有沒有發生變化,沒發生變化我就會更新成功,發生變化了就不會更新成功我再去重試之前的動作直到更新成功。

鎖應用

使用悲觀鎖的時候我們首先必須關閉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會鎖定資料,防止其他事務更改資料。但是鎖定資料也是有規則的。

查詢條件與鎖定範圍:

1、具體的主鍵值為查詢條件

比如查詢條件為主鍵ID=1等等,如果此條資料存在,則鎖定目前行資料,如果不存在,則不鎖定。

2、不具體的主鍵值為查詢條件

比如查詢條件為主鍵ID>1等等,此時會鎖定整張資料表。

3、查詢條件中無主鍵

會鎖定整張資料表。

4、如果查詢條件中使用了索引為查詢條件

明确指定索引并且查到,則鎖定整條資料。如果找不到指定索引資料,則不加鎖。

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,此時事務二就會操作失敗,進而保證了資料的正确性。

2、使用時間戳來實作,原理同上。

3、使用其他資料庫字段,如:金額,更新時添加條件判斷金額是否變化,原理同上。

樂觀鎖圖示

結論

兩種鎖各有優缺點,不能單純的定義哪個好于哪個。樂觀鎖比較适合資料修改比較少,讀取比較頻繁的場景,即使出現了少量的沖突,這樣也省去了大量的鎖的開銷,故而提高了系統的吞吐量。但是如果經常發生沖突(寫資料比較多的情況下),上層應用不不斷的retry,這樣反而降低了性能,對于這種情況使用悲觀鎖就更合适。