mysql樂觀鎖和悲觀鎖詳解

mysql樂觀鎖和悲觀鎖詳解

相信不少朋友在面試的時候,都會被問到樂觀鎖和悲觀鎖的問題,若是不清楚其概念和用法的狀況下,相信不少朋友都會感受很懵逼,那麼面試的結果也就不言而喻了。
那麼樂觀鎖和悲觀鎖究竟是個什麼東西,用它能來作什麼呢?
                相信你們都遇到這種場景,當不少人(一兩我的估計不行)同時對同一條數據作修改的時候,那麼數據的最終結果是怎樣的呢?
這也就是咱們說的併發狀況,這樣會致使如下兩種結果:html

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

這樣的問題怎麼解決呢?因而乎,鎖就這樣產生了,鎖分爲樂觀鎖和悲觀鎖,它的目的是用來解決併發控制的問題。
MyISAM引擎不支持事務,因此不考慮它有樂觀鎖和悲觀鎖概念。MyISAM只有表鎖,鎖又分爲讀鎖和寫鎖。在這裏咱們只討論InnoDB引擎。
須要注意的是,樂觀鎖和悲觀鎖並非解決併發控制的惟一手段(也可使用消息中間件kafka,MQ之類的做爲緩衝等等),並且樂觀鎖和悲觀鎖並不只限制在mysql中使用,它是一種概念,不少其餘的應用,如redis,memcached等,只要存在併發狀況的,均可以應用這種概念,只是方式上有些差異而已。mysql


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

//1.查詢出商品信息 redis

select status,version from t_goods where id=#{id} sql

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

//3.修改商品status爲2 session

update t_goods 併發

set status=2,version=version+1 memcached

where id=#{id} and version=#{version};
性能

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

下面這種極端的狀況:

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

2、悲觀鎖
悲觀鎖,簡單地說,就是從數據庫層面上作併發控制,去加鎖。
悲觀鎖的實現方式有兩種:共享鎖(讀鎖)排它鎖(寫鎖)
共享鎖(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

相關文章
相關標籤/搜索