mysql樂觀鎖和悲觀鎖詳解


mysql樂觀鎖和悲觀鎖詳解

相信很多朋友在面試的時候,都會被問到樂觀鎖和悲觀鎖的問題,如果不清楚其概念和用法的情況下,相信很多朋友都會感覺很懵逼,那么面試的結果也就不言而喻了。
那么樂觀鎖和悲觀鎖到底是個什么東西,用它能來做什么呢?
                相信大家都遇到這種場景,當很多人(一兩個人估計不行)同時對同一條數據做修改的時候,那么數據的最終結果是怎樣的呢?
這也就是我們說的並發情況,這樣會導致以下兩種結果:

  1. 更新錯誤,你修改之后的數據可能被別人覆蓋了,導致你很懵逼,甚至懷疑自己開發的功能是否有問題;
  2. 臟讀,數據更新錯誤,導致讀數據也是錯的,查詢出一些默認奇妙的數據,看到的不是你自己修改的結果。

這樣的問題怎么解決呢?於是乎,鎖就這樣產生了,鎖分為樂觀鎖和悲觀鎖,它的目的是用來解決並發控制的問題。
MyISAM引擎不支持事務,所以不考慮它有樂觀鎖和悲觀鎖概念。MyISAM只有表鎖,鎖又分為讀鎖和寫鎖。在這里我們只討論InnoDB引擎。
需要注意的是,樂觀鎖和悲觀鎖並不是解決並發控制的唯一手段(也可以使用消息中間件kafka,MQ之類的作為緩沖等等),而且樂觀鎖和悲觀鎖並不僅限制在mysql中使用,它是一種概念,很多其他的應用,如redis,memcached等,只要存在並發情況的,都可以應用這種概念,只是方式上有些差別而已。


一、樂觀鎖
樂觀鎖,簡單地說,就是從應用系統層面上做並發控制,去加鎖。
實現樂觀鎖常見的方式:版本號version
實現方式,在數據表中增加版本號字段,每次對一條數據做更新之前,先查出該條數據的版本號,每次更新數據都會對版本號進行更新。在更新時,把之前查出的版本號跟庫中數據的版本號進行比對,如果相同,則說明該條數據沒有被修改過,執行更新。如果比對的結果是不一致的,則說明該條數據已經被其他人修改過了,則不更新,客戶端進行相應的操作提醒。
使用版本號實現樂觀鎖
使用版本號時,可以在數據初始化時指定一個版本號,每次對數據的更新操作都對版本號執行+1操作。並判斷當前版本號是不是該數據的最新的版本號。 

//1.查詢出商品信息 

select 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};

注意第二個事務執行update時,第一個事務已經提交了,所以第二個事務能夠讀取到第一個事務修改的version。

下面這種極端的情況:

我們知道MySQL數據庫引擎InnoDB,事務的隔離級別是Repeatable Read,因此是不會出現臟讀、不可重復讀。
在這種極端情況下,第二個事務的update由於不能讀取第一個事務未提交的數據(第一個事務已經對這一條數據加了排他鎖,第二個事務需要等待獲取鎖),第二個事務獲取了排他鎖后,會發現version已經發生了改變從而提交失敗。

二、悲觀鎖
悲觀鎖,簡單地說,就是從數據庫層面上做並發控制,去加鎖。
悲觀鎖的實現方式有兩種:共享鎖(讀鎖)排它鎖(寫鎖)
共享鎖(IS鎖),實現方式是在sql后加LOCK IN SHARE MODE,比如SELECT ... LOCK IN SHARE MODE,即在符合條件的rows上都加了共享鎖,這樣的話,其他session可以讀取這些記錄,也可以繼續添加                       IS鎖,但是無法修改這些記錄直到你這個加鎖的session執行完成(否則直接鎖等待超時)。
排它鎖(IX鎖),實現方式是在sql后加FOR UPDATE,比如SELECT ... FOR UPDATE ,即在符合條件的rows上都加了排它鎖,其他session也就無法在這些記錄上添加任何的S鎖或X鎖。如果不存在一致性非                         鎖定讀的話,那么其他session是無法讀取和修改這些記錄的,但是innodb有非鎖定讀(快照讀並不需要加鎖),for update之后並不會阻塞其他session的快照讀取操作,除了select ...lock in share mode和select ... for update這種顯示加鎖的查詢操作。

通過對比,發現for update的加鎖方式無非是比lock in share mode的方式多阻塞了select...lock in share mode的查詢方式,並不會阻塞快照讀

mysql InnoDB引擎默認的修改數據語句:update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型。

在Java中,synchronized的思想也是悲觀鎖。

以排它鎖為例

要使用悲觀鎖,我們必須關閉mysql數據庫的自動提交屬性,因為MySQL默認使用auto commit模式,也就是說,當你執行一個更新操作后,MySQL會立刻將結果進行提交。set autocommit=0; 

//0.開始事務 

begin;/begin work;/start transaction; (三者選一就可以) 

//1.查詢出商品信息 

select status from t_goods where id=1 for update; 

//2.根據商品信息生成訂單 

insert into t_orders (id,goods_id) values (null,1); 

//3.修改商品status為2 

update t_goods set status=2; 

//4.提交事務 

commit;/commit work;

上面的查詢語句中,我們使用了select…for update的方式, 這樣就通過開啟排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id為1的 那條數據就被我們鎖定了,其它的事務必須等本次事務提交之后才能執行。這樣我們可以保證當前的數據不會被其它事務修改。

補充:
1.MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作 (UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖。
2.MySQL InnoDB默認行級鎖。 行級鎖都是基於索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住。
3.從上面對兩種鎖的介紹,我們知道兩種鎖各有優缺點,不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況下(多讀場景),即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果是多寫的情況,一般會經常產生沖突,這就會導致上層應用會不斷的進行retry,這樣反倒是降低了性能,所以一般多寫的場景下用悲觀鎖就比較合適。

參考:
http://www.cnblogs.com/exceptioneye/p/5373477.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM