并发控制--悲观锁和乐观锁详解
背景
考慮下面兩個(gè)并發(fā)帶來(lái)的問(wèn)題:
1、丟失更新:一個(gè)事務(wù)的更新結(jié)果覆蓋了其它事務(wù)的更新結(jié)果,即所謂的更新丟失。
2、臟讀:當(dāng)一個(gè)事務(wù)讀取其它完成一半事務(wù)的記錄時(shí),就會(huì)發(fā)生臟讀取。
例如:
兩個(gè)用戶同時(shí)修改商品庫(kù)存表,A、B同時(shí)進(jìn)入,看到的庫(kù)存都是100,A購(gòu)買一件把庫(kù)存修改為99(100-1)。此時(shí)B購(gòu)買兩件把庫(kù)存修改為98(100-2),因?yàn)锳、B同時(shí)讀到的庫(kù)存都是100,B并不能看到A做的庫(kù)存更新,所以造成B臟讀,造成A丟失更新。
所以為了解決這些并發(fā)帶來(lái)的問(wèn)題。 我們需要引入并發(fā)控制機(jī)制--鎖。
?
鎖分類
悲觀鎖
悲觀鎖就是用戶修改數(shù)據(jù)時(shí)看起來(lái)很悲觀,保守態(tài)度,擔(dān)心別的用戶會(huì)同時(shí)修改這條數(shù)據(jù),所以每次修改時(shí)會(huì)提前把這條數(shù)據(jù)鎖定起來(lái),只有自己可修改(但別的用戶可以讀),等自己修改完了再釋放鎖。
?
樂(lè)觀鎖
樂(lè)觀鎖就是用戶修改數(shù)據(jù)時(shí)心態(tài)很樂(lè)觀,不管別人修改不修改數(shù)據(jù),我都不上鎖,我修改的時(shí)候判斷下數(shù)據(jù)有沒(méi)有發(fā)生變化,沒(méi)發(fā)生變化我就會(huì)更新成功,發(fā)生變化了就不會(huì)更新成功我再去重試之前的動(dòng)作直到更新成功。
?
鎖應(yīng)用
悲觀鎖
使用悲觀鎖的時(shí)候我們首先必須關(guān)閉mysql數(shù)據(jù)庫(kù)的自動(dòng)提交屬性,因?yàn)镸ySQL默認(rèn)使用autocommit模式,也就是說(shuō),當(dāng)你執(zhí)行一個(gè)更新操作后,MySQL會(huì)立刻將結(jié)果進(jìn)行提交。
關(guān)閉命令為:set autocommit=0;
?
悲觀鎖一般使用select…for update實(shí)現(xiàn),在執(zhí)行的時(shí)候會(huì)鎖定數(shù)據(jù),雖然會(huì)鎖定數(shù)據(jù),但是不影響其他事務(wù)的普通查詢使用。
在我們使用悲觀鎖的時(shí)候事務(wù)中的語(yǔ)句例如:
//開(kāi)始事務(wù) begin;/begin work;/start transaction; (三選一)//查詢信息 select * from order where id=1 for update;//修改信息 update order set name='names';//提交事務(wù) commit;/commit work;(二選一)此處的查詢語(yǔ)句for update關(guān)鍵字,在事務(wù)中只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一條數(shù)據(jù)時(shí)會(huì)等待其它事務(wù)結(jié)束后才執(zhí)行,一般的SELECT查詢則不受影響。
?
注意事項(xiàng)
執(zhí)行事務(wù)時(shí)關(guān)鍵字select…for update會(huì)鎖定數(shù)據(jù),防止其他事務(wù)更改數(shù)據(jù)。但是鎖定數(shù)據(jù)也是有規(guī)則的。
?
查詢條件與鎖定范圍:
1、具體的主鍵值為查詢條件
比如查詢條件為主鍵ID=1等等,如果此條數(shù)據(jù)存在,則鎖定當(dāng)前行數(shù)據(jù),如果不存在,則不鎖定。
?
2、不具體的主鍵值為查詢條件
比如查詢條件為主鍵ID>1等等,此時(shí)會(huì)鎖定整張數(shù)據(jù)表。
?
3、查詢條件中無(wú)主鍵
會(huì)鎖定整張數(shù)據(jù)表。
?
4、如果查詢條件中使用了索引為查詢條件
明確指定索引并且查到,則鎖定整條數(shù)據(jù)。如果找不到指定索引數(shù)據(jù),則不加鎖。
?
樂(lè)觀鎖
1、使用自增長(zhǎng)的整數(shù)表示數(shù)據(jù)版本號(hào),更新時(shí)檢查版本號(hào)是否一致,比如數(shù)據(jù)庫(kù)中數(shù)據(jù)版本為666,更新提交時(shí)version=666+1,使用該version值(=667)與數(shù)據(jù)庫(kù)version+1(=667)作比較,如果相等,則可以更新,如果不等則有可能其他程序已更新該記錄,所以返回錯(cuò)誤或者發(fā)起重試動(dòng)作。
?
例如表
student(id,name,version)
1 ? ? a ? ? ? 1
當(dāng)事務(wù)一進(jìn)行更新操作:update student set name='txt' where id = #{id} and version = #{version};
此時(shí)操作完后數(shù)據(jù)會(huì)變?yōu)閕d = 1,name = txt,version = 2,當(dāng)另外一個(gè)事務(wù)二同樣執(zhí)行更新操作的時(shí)候,卻發(fā)現(xiàn)version != 1,此時(shí)事務(wù)二就會(huì)操作失敗,從而保證了數(shù)據(jù)的正確性。
2、使用時(shí)間戳來(lái)實(shí)現(xiàn),原理同上。
3、使用其他數(shù)據(jù)庫(kù)字段,如:金額,更新時(shí)添加條件判斷金額是否變化,原理同上。
?
樂(lè)觀鎖圖示
?
結(jié)論
兩種鎖各有優(yōu)缺點(diǎn),不能單純的定義哪個(gè)好于哪個(gè)。樂(lè)觀鎖比較適合數(shù)據(jù)修改比較少,讀取比較頻繁的場(chǎng)景,即使出現(xiàn)了少量的沖突,這樣也省去了大量的鎖的開(kāi)銷,故而提高了系統(tǒng)的吞吐量。但是如果經(jīng)常發(fā)生沖突(寫數(shù)據(jù)比較多的情況下),上層應(yīng)用不不斷的retry,這樣反而降低了性能,對(duì)于這種情況使用悲觀鎖就更合適。
總結(jié)
以上是生活随笔為你收集整理的并发控制--悲观锁和乐观锁详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java高级应用:线程池全面解析
- 下一篇: 掌握这 11 个方法论,搞定一场完美技术