mysql樂觀鎖和悲觀鎖詳解
相信很多朋友在面試的時候,都會被問到樂觀鎖和悲觀鎖的問題,如果不清楚其概念和用法的情況下,相信很多朋友都會感覺很懵逼,那么面試的結果也就不言而喻了,
那么樂觀鎖和悲觀鎖到底是個什么東西,用它能來做什么呢?
相信大家都遇到這種場景,當很多人(一兩個人估計不行)同時對同一條資料做修改的時候,那么資料的最終結果是怎樣的呢?
這也就是我們說的并發情況,這樣會導致以下兩種結果:
- 更新錯誤,你修改之后的資料可能被別人覆寫了,導致你很懵逼,甚至懷疑自己開發的功能是否有問題;
- 臟讀,資料更新錯誤,導致讀資料也是錯的,查詢出一些默認奇妙的資料,看到的不是你自己修改的結果,
這樣的問題怎么解決呢?于是乎,鎖就這樣產生了,鎖分為樂觀鎖和悲觀鎖,它的目的是用來解決并發控制的問題,
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
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/252991.html
標籤:其他
上一篇:TTL 生存時間
