圖文并茂的帶你徹底理解悲觀鎖與樂觀鎖
這是一篇介紹悲觀鎖和樂觀鎖的入門文章。旨在讓那些不了解悲觀鎖和樂觀鎖的小白們弄清楚什么是悲觀鎖,什么是樂觀鎖。不同于其他文章,本文會配上相應(yīng)的圖解讓大家更容易理解。通過該文,你會學(xué)習(xí)到如下的知識。
1.鎖(Lock)
在介紹悲觀鎖和樂觀鎖之前,讓我們看一下什么是鎖。
鎖,在我們生活中隨處可見,我們的門上有鎖,我們存錢的保險柜上有鎖,是用來保護我們財產(chǎn)安全的。
程序中也有鎖,當(dāng)多個線程修改共享變量時,我們可以給修改操作上鎖(syncronized)。
當(dāng)多個用戶修改表中同一數(shù)據(jù)時,我們可以給該行數(shù)據(jù)上鎖(行鎖)。因此,鎖其實是在并發(fā)下控制多個操作的順序執(zhí)行,以此來保證數(shù)據(jù)安全的變動。
并且,鎖是一種保證數(shù)據(jù)安全的機制和手段,而并不是特定于某項技術(shù)的。悲觀鎖和樂觀鎖亦是如此。本篇介紹的悲觀鎖和樂觀鎖是基于數(shù)據(jù)庫層面的。
2.悲觀鎖
悲觀鎖(Pessimistic Concurrency Control),***眼看到它,相信每個人都會想到這是一個悲觀的鎖。沒錯,它就是一個悲觀的鎖。
那這個悲觀體現(xiàn)在什么地方呢?悲觀是我們?nèi)祟愐环N消極的情緒,對應(yīng)到鎖的悲觀情緒,悲觀鎖認(rèn)為被它保護的數(shù)據(jù)是極其不安全的,每時每刻都有可能變動,一個事務(wù)拿到悲觀鎖后(可以理解為一個用戶),其他任何事務(wù)都不能對該數(shù)據(jù)進(jìn)行修改,只能等待鎖被釋放才可以執(zhí)行。
數(shù)據(jù)庫中的行鎖,表鎖,讀鎖,寫鎖,以及syncronized實現(xiàn)的鎖均為悲觀鎖。
這里再介紹一下什么是數(shù)據(jù)庫的表鎖和行鎖,以免有的同學(xué)對后面悲觀鎖的實現(xiàn)看不明白。
我們經(jīng)常使用的數(shù)據(jù)庫是mysql,mysql中最常用的引擎是Innodb,Innodb默認(rèn)使用的是行鎖。而行鎖是基于索引的,因此要想加上行鎖,在加鎖時必須***索引,否則將使用表鎖。
3.樂觀鎖
與悲觀相對應(yīng),樂觀是我們?nèi)祟愐环N積極的情緒。樂觀鎖(Optimistic Concurrency Control)的“樂觀情緒”體現(xiàn)在,它認(rèn)為數(shù)據(jù)的變動不會太頻繁。因此,它允許多個事務(wù)同時對數(shù)據(jù)進(jìn)行變動。
但是,樂觀不代表不負(fù)責(zé),那么怎么去負(fù)責(zé)多個事務(wù)順序?qū)?shù)據(jù)進(jìn)行修改呢?
樂觀鎖通常是通過在表中增加一個版本(version)或時間戳(timestamp)來實現(xiàn),其中,版本最為常用。
事務(wù)在從數(shù)據(jù)庫中取數(shù)據(jù)時,會將該數(shù)據(jù)的版本也取出來(v1),當(dāng)事務(wù)對數(shù)據(jù)變動完畢想要將其更新到表中時,會將之前取出的版本v1與數(shù)據(jù)中***的版本v2相對比,如果v1=v2,那么說明在數(shù)據(jù)變動期間,沒有其他事務(wù)對數(shù)據(jù)進(jìn)行修改,此時,就允許事務(wù)對表中的數(shù)據(jù)進(jìn)行修改,并且修改時version會加1,以此來表明數(shù)據(jù)已被變動。
如果,v1不等于v2,那么說明數(shù)據(jù)變動期間,數(shù)據(jù)被其他事務(wù)改動了,此時不允許數(shù)據(jù)更新到表中,一般的處理辦法是通知用戶讓其重新操作。不同于悲觀鎖,樂觀鎖是人為控制的。
4.如何實現(xiàn)
經(jīng)過上面的學(xué)習(xí),我們知道悲觀鎖和樂觀鎖是用來控制并發(fā)下數(shù)據(jù)的順序變動問題的。那么我們就模擬一個需要加鎖的場景,來看不加鎖會出什么問題,并且怎么利用悲觀鎖和樂觀鎖去解決。
場景:A和B用戶最近都想吃豬肉脯,于是他們打開了購物網(wǎng)站,并且找到了同一家賣豬肉脯的>店鋪。下面是這個店鋪的商品表goods結(jié)構(gòu)和表中的數(shù)據(jù)。
從表中可以看到豬肉脯目前的數(shù)量只有1個了。在不加鎖的情況下,如果A,B同時下單,就有可能導(dǎo)致超賣。
悲觀鎖解決
利用悲觀鎖的解決思路是,我們認(rèn)為數(shù)據(jù)修改產(chǎn)生沖突的概率比較大,所以在更新之前,我們顯示的對要修改的記錄進(jìn)行加鎖,直到自己修改完再釋放鎖。加鎖期間只有自己可以進(jìn)行讀寫,其他事務(wù)只能讀不能寫。
A下單前先給豬肉脯這行數(shù)據(jù)(id=1)加上悲觀鎖(行鎖)。此時這行數(shù)據(jù)只能A來操作,也就是只有A能買。B想買就必須一直等待。
當(dāng)A買好后,B再想去買的時候會發(fā)現(xiàn)數(shù)量已經(jīng)為0,那么B看到后就會放棄購買。
那么如何給豬肉脯也就是id=1這條數(shù)據(jù)加上悲觀鎖鎖呢?我們可以通過以下語句給id=1的這行數(shù)據(jù)加上悲觀鎖
- select num from goods where id = 1 for update;
下面是悲觀鎖的加鎖圖解
我們通過開啟mysql的兩個會話,也就是兩個命令行來演示。
1、事務(wù)A執(zhí)行命令給id=1的數(shù)據(jù)上悲觀鎖準(zhǔn)備更新數(shù)據(jù)
這里之所以要以begin開始,是因為mysql是自提交的,所以要以begin開啟事務(wù),否則所有修改將被mysql自動提交。
2、事務(wù)B也去給id=1的數(shù)據(jù)上悲觀鎖準(zhǔn)備更新數(shù)據(jù)
我們可以看到此時事務(wù)B再一直等待A釋放鎖。如果A長期不釋放鎖,那么最終事務(wù)B將會報錯,這有興趣的可以去嘗試一下。
3、接著我們讓事務(wù)A執(zhí)行命令去修改數(shù)據(jù),讓豬肉脯的數(shù)量減一,然后查看修改后的數(shù)據(jù),***commit,結(jié)束事務(wù)。
我們可以看到,此時***一個豬肉脯被A買走,只剩0個了。
4、當(dāng)事務(wù)A執(zhí)行完第3步后,我們看事務(wù)B中出現(xiàn)了什么
我們看到由于事務(wù)A釋放了鎖,事務(wù)B就結(jié)束了等待,拿到了鎖,但是數(shù)據(jù)此時變成了0,那么B看到后就知道被買走了,就會放棄購買。
通過悲觀鎖,我們解決了豬肉脯購買的問題。
樂觀鎖解決
下面,我們利用樂觀鎖來解決該問題。上面樂觀鎖的介紹中,我們提到了,樂觀鎖是通過版本號version來實現(xiàn)的。所以,我們需要給goods表加上version字段,表變動后的結(jié)構(gòu)如下:
使用樂觀鎖的解決思路是,我們認(rèn)為數(shù)據(jù)修改產(chǎn)生沖突的概率并不大,多個事務(wù)在修改數(shù)據(jù)的之前先查出版本號,在修改時把當(dāng)前版本號作為修改條件,只會有一個事務(wù)可以修改成功,其他事務(wù)則會失敗。
A和B同時將豬肉脯(id=1下面都說是id=1)的數(shù)據(jù)查出來,然后A先買,A將id=1和version=0作為條件進(jìn)行數(shù)據(jù)更新,即將數(shù)量-1,并且將版本號+1。
此時版本號變?yōu)?。A此時就完成了商品的購買。***B開始買,B也將id=1和version=0作為條件進(jìn)行數(shù)據(jù)更新,但是更新完后,發(fā)現(xiàn)更新的數(shù)據(jù)行數(shù)為0,此時就說明已經(jīng)有人改動過數(shù)據(jù),此時就應(yīng)該提示用戶重新查看***數(shù)據(jù)購買。
下面是樂觀鎖的加鎖圖解
我們還是通過開啟mysql的兩個會話,也就是兩個命令行來演示。
1、事務(wù)A執(zhí)行查詢命令,事務(wù)B執(zhí)行查詢命令,因為兩者查詢的結(jié)果相同,所以下面我只列出一個截圖。
此時A和B均獲取到相同的數(shù)據(jù)
2、事務(wù)A進(jìn)行購買更新數(shù)據(jù),然后再查詢更新后的數(shù)據(jù)。
我們可以看到事務(wù)A成功更新了數(shù)據(jù)和版本號。
事務(wù)B再進(jìn)行購買更新數(shù)據(jù),然后我們看影響行數(shù)和更新后的數(shù)據(jù)
可以看到最終修改行數(shù)為0,數(shù)據(jù)沒有改變。此時就需要我們告知用戶重新處理。
5.優(yōu)缺點
下面我們介紹下樂觀鎖和悲觀鎖的優(yōu)缺點以便我們分析他們的應(yīng)用場景,這里我只分析最重要的優(yōu)缺點,也是我們要記住的。
悲觀鎖
- 優(yōu)點:悲觀鎖利用數(shù)據(jù)庫中的鎖機制來實現(xiàn)數(shù)據(jù)變化的順序執(zhí)行,這是最有效的辦法
- 缺點:一個事務(wù)用悲觀鎖對數(shù)據(jù)加鎖之后,其他事務(wù)將不能對加鎖的數(shù)據(jù)進(jìn)行除了查詢以外的所有操作,如果該事務(wù)執(zhí)行時間很長,那么其他事務(wù)將一直等待,那勢必影響我們系統(tǒng)的吞吐量。
樂觀鎖
- 優(yōu)點:樂觀鎖不在數(shù)據(jù)庫上加鎖,任何事務(wù)都可以對數(shù)據(jù)進(jìn)行操作,在更新時才進(jìn)行校驗,這樣就避免了悲觀鎖造成的吞吐量下降的劣勢。
- 缺點:樂觀鎖因為是通過我們?nèi)藶閷崿F(xiàn)的,它僅僅適用于我們自己業(yè)務(wù)中,如果有外來事務(wù)插入,那么就可能發(fā)生錯誤。
6.應(yīng)用場景
悲觀鎖:因為悲觀鎖會影響系統(tǒng)吞吐的性能,所以適合應(yīng)用在寫為居多的場景下。
樂觀鎖:因為樂觀鎖就是為了避免悲觀鎖的弊端出現(xiàn)的,所以適合應(yīng)用在讀為居多的場景下。
參考資料
https://chenzhou123520.iteye.com/blog/1860954
https://baike.baidu.com/item/樂觀鎖/7146502
【本文是51CTO專欄作者Hollis的原創(chuàng)文章,作者微信公眾號Hollis(ID:hollischuang)】












































