1. 程式人生 > >樂觀鎖、悲觀鎖、共享鎖以及排它鎖的概念

樂觀鎖、悲觀鎖、共享鎖以及排它鎖的概念

樂觀鎖

樂觀併發控制(又名”樂觀鎖”,Optimistic Concurrency Control,縮寫”OCC”),它假設多使用者併發的事務在處理時不會彼此互相影響,各事務能夠在不產生鎖的情況下處理各自影響的那部分資料。在提交資料更新之前,每個事務會先檢查在該事務讀取資料後,有沒有其他事務又修改了該資料。如果其他事務有更新的話,正在提交的事務會進行回滾。簡而言之,樂觀鎖總是認為不會產生併發問題,每次去取資料的時候總認為不會有其他執行緒對資料進行修改,因此不會上鎖,但是在更新時會判斷其他執行緒在這之前有沒有對資料進行修改,一般會使用版本號機制或CAS操作實現。

CAS操作方式:即compare and swap 或者 compare and set,涉及到三個運算元,資料所在的記憶體值,預期值,新值。當需要更新時,判斷當前記憶體值與之前取到的值是否相等,若相等,則用新值更新,若失敗則重試,一般情況下是一個自旋操作,即不斷的重試。

樂觀鎖大多數情況下用於資料爭用不大、衝突較少的環境中,這是因為偶爾回滾事務的成本會低於讀取資料時鎖定資料的成本。這樣做可以獲得比其他併發控制方法更高的吞吐量。相對於悲觀鎖,在對資料庫進行處理的時候,樂觀鎖並不會使用資料庫提供的鎖機制。一般的實現樂觀鎖的方式就是記錄資料版本。

資料版本為資料增加的一個版本標識。當讀取資料時,將版本標識的值一同讀出,資料每更新一次,同時對版本標識進行更新。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的版本標識進行比對,如果資料庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認為是過期資料。實現資料版本有兩種方式,第一種是使用版本號,第二種是使用時間戳。

使用版本號實現樂觀鎖

版本號方式:一般是在資料表中加上一個資料版本號version欄位,表示資料被修改的次數當資料被修改時,version值會加1。當執行緒A要更新資料值時,在讀取資料的同時也會讀取version值,在提交更新時,若剛才讀取到的version值為當前資料庫中的version值相等時才更新,否則重試更新操作,直到更新成功。

核心SQL語句:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

例子:

(1)查詢出商品資訊
select (status,status,version) from t_goods where id=#{id};
(2)根據商品資訊生成訂單
(3)修改商品status為2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};

樂觀鎖是一種樂觀陽光的併發控制方法,它認為事務之間的資料競爭的概率比較小,因此儘可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。

悲觀鎖

悲觀併發控制(又名”悲觀鎖”,Pessimistic Concurrency Control,縮寫”PCC”)。它可以阻止一個事務以影響其他使用者的方式來修改資料。如果一個事務執行的操作讀某行資料應用了鎖,那只有當這個事務把鎖釋放,其他事務才能夠執行與該鎖衝突的操作。 悲觀併發控制主要用於資料爭用激烈的環境,以及發生併發衝突時使用鎖保護資料的成本要低於回滾事務的成本的環境中。 悲觀鎖總是假設最壞的情況,每次取資料時都認為其他執行緒會修改,所以都會加鎖(讀鎖、寫鎖、行鎖等),當其他執行緒想要訪問資料時,都需要阻塞掛起。可以依靠資料庫實現,如行鎖、讀鎖和寫鎖等,都是在操作之前加鎖。要使用悲觀鎖,我們必須關閉mysql資料庫的自動提交屬性,因為MySQL預設使用autocommit模式,也就是說,當你執行一個更新操作後,MySQL會立刻將結果進行提交set autocommit=0;

例子:

(1)開始事務
begin;/begin work;/start transaction; (三者選一)
(2)查詢出商品資訊
select status from t_goods where id=1 for update;
(3)根據商品資訊生成訂單
insert into t_orders (id,goods_id) values (null,1);
(4)修改商品status為2
update t_goods set status=2;
(5)提交事務
commit;/commit work;

上面使用select status from t_goods where id=1 for update;會把資料給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB預設行級鎖行級鎖都是基於索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住,這點需要注意。

悲觀鎖實際上是一種過於保守策略,為資料處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓資料庫產生額外的開銷,還有增加產生死鎖的概率;另外,在只讀型事務處理中由於不會產生衝突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了並行性,一個事務如果鎖定了某行資料,其他事務就必須等待該事務處理完才可以處理那行數

總結:讀取頻繁使用樂觀鎖,寫入頻繁使用悲觀鎖。

 

共享鎖【S鎖】

又稱讀鎖,若事務T對資料物件A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。共享鎖下其它使用者可以併發讀取,查詢資料。但不能修改,增加,刪除資料。

共享鎖的寫法:lock in share mode
例如:select English from Asd where English>60 lock in share mode;

排他鎖【X鎖】

又稱寫鎖。若事務T對資料物件A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A。

排它鎖的寫法:for update
例如:select Math from Asd where Math>60 for update;

共享鎖的使用:

在第一個連線中執行以下語句
begin tran
select * from table1 holdlock -holdlock人為加鎖
where B='b2'
waitfor delay '00:00:30' --等待30秒
commit tran

在第二個連線中執行以下語句
begin tran
select A,C from table1
where B='b2'
update table1
set A='aa'
where B='b2'
commit tran
若同時執行上述兩個語句,則第二個連線中的select查詢可以執行
而update必須等待第一個事務釋放共享鎖轉為排它鎖後才能執行 即要等待30秒

排它鎖的使用:

在第一個連線中執行以下語句 
begin tran 
update table1 
set A='aa' 
where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran 

在第二個連線中執行以下語句 
begin tran 
select * from table1 
where B='b2' 
commit tran 
若同時執行上述兩個語句,則select查詢必須等待update執行完畢才能執行即要等待30秒

參考:

https://blog.csdn.net/puhaiyang/article/details/72284702

https://blog.csdn.net/L_BestCoder/article/details/79298417

https://blog.csdn.net/stuShan/article/details/51296098