漫谈悲观锁乐观锁
關(guān)注公眾號【高性能架構(gòu)探索】,后臺回復(fù)【pdf】,免費獲取計算機(jī)必備經(jīng)典書籍
概念
悲觀鎖(Pessimistic?Lock)
總是假設(shè)最壞的情況,每次拿數(shù)據(jù)的時候,都認(rèn)為別人也會修改,所以每次都會加鎖。當(dāng)要對數(shù)據(jù)庫中的一條數(shù)據(jù)進(jìn)行修改的時候,為了避免同時被其他人修改,最好的辦法就是直接對該數(shù)據(jù)進(jìn)行加鎖以防止并發(fā)。這種借助數(shù)據(jù)庫鎖機(jī)制,在修改數(shù)據(jù)之前先鎖定,再修改的方式被稱之為悲觀并發(fā)控制【Pessimistic?Concurrency?Control,縮寫“PCC”,又名“悲觀鎖”】。
悲觀鎖,正如其名,具有強(qiáng)烈的獨占和排他特性。它指的是對數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度。因此,在整個數(shù)據(jù)處理過程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性,否則,即使在本系統(tǒng)中實現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會修改數(shù)據(jù))。
樂觀鎖(Optimistic?Locking)
樂觀鎖是相對悲觀鎖而言的,總是假設(shè)最好的情況,每次拿數(shù)據(jù)的時候,都認(rèn)為別人不會修改。但是在更新數(shù)據(jù)的時候,會判斷再次期間有沒有人去修改這個數(shù)據(jù),如果發(fā)現(xiàn)被修改了即產(chǎn)生了沖突,則返回給用戶錯誤的信息,讓用戶決定如何去做。樂觀鎖適用于讀操作多的場景,這樣可以提高程序的吞吐量。
基本原理
實現(xiàn)方式
悲觀鎖
悲觀鎖的實現(xiàn)是依賴于數(shù)據(jù)庫提供的鎖機(jī)制,流程如下:
修改記錄前,對記錄加上排他鎖(exclusive?locking)
如果加鎖失敗,說明這條數(shù)據(jù)正在被修改,那么當(dāng)前查詢要等待或者拋出異常,這由開發(fā)者決定
如果加鎖成功,可以對這條數(shù)據(jù)修改了,事務(wù)完成解鎖
加鎖修改期間,其他事務(wù)也想對這條記錄進(jìn)行操作時,都要等待或者直接拋出異常
注意:在使用?mysql?Innodb?引擎實現(xiàn)悲觀鎖時,必須關(guān)閉?mysql?的自動提交屬性,因為?MySQL?默認(rèn)使用?autocommit?模式,也就是說,當(dāng)你執(zhí)行一個更新操作后,MySQL?會立刻將結(jié)果進(jìn)行提交。執(zhí)行:set?autocommit?=?0;
樂觀鎖
通常有兩種實現(xiàn)方式:
1、版本號方式,由用戶來實現(xiàn)
2、CAS(Compare?And?Set)
下面以一個例子來理解悲觀鎖和樂觀鎖的機(jī)制
A和B用戶最近都想吃豬肉脯,于是他們打開了購物網(wǎng)站,并且找到了同一家賣豬肉脯的店鋪。
該店鋪的商品表goods和表數(shù)據(jù)如下:
| id | name | num |
| 1 | 豬肉脯 | 1 |
| 2 | 牛肉干 | 1 |
從表中可以看到豬肉脯目前的數(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的兩個會話來分析整個悲觀鎖的執(zhí)行過程
1、事務(wù)A執(zhí)行命令給id?=?1的數(shù)據(jù)上悲觀鎖準(zhǔn)備更新數(shù)據(jù)
//0.開始事務(wù)begin; //1.查詢出商品庫存信息select num from goods where id = 1 for update;//2.修改商品庫存為2update?goods?set?num?=?0?where?id?=?1;//3.提交事務(wù)commit;2、事務(wù)B也去給id?=?1的數(shù)據(jù)上悲觀鎖準(zhǔn)備更新數(shù)據(jù)
//0.開始事務(wù)begin; //1.查詢出商品庫存信息select num from goods where id = 1 for update;事務(wù)B將會阻塞,一直在等待A釋放鎖,如果A長期不釋放鎖,那么最終事務(wù)B將會報錯(超時)
3、當(dāng)事務(wù)A執(zhí)行完成后,事務(wù)B拿到了鎖,此時獲取到的商品數(shù)為0,那么B看到后,就知道沒有庫存了。
樂觀鎖解決:
版本號方式
| id | name | num | version | |
| 1 | 豬肉脯 | 1 | 0 | |
| 2 | 牛肉干 | 1 | 0 |
使用樂觀鎖的解決思路是,我們認(rèn)為數(shù)據(jù)修改產(chǎn)生的沖突的概率不大,多個事務(wù)在修改數(shù)據(jù)之前,先查出版本號,在修改的時候,把當(dāng)前版本號作為修改條件,只會有一個事務(wù)可以修改成功,其他事務(wù)則會失敗
A和B同事將id?=?1的豬肉鋪的數(shù)據(jù)查出來,然后A先買,A將id?=?1的version=?1作為條件進(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ù)購買。
1、A事務(wù)和B事務(wù)同時查找id?=?1?的數(shù)量和版本號
select?num,?version?from?goods?where?id?= 1;//此時得到的值分別為1 和 02、事務(wù)A進(jìn)行購買
update?goods?set?num?=?num?-?1,?version?=?version?+?1?where?id?=?1;select?num,?version?from?goods?where?id?=?1;//此時得到的值為0和13、事務(wù)B進(jìn)行購買
update?goods?set?num?=?num?-?1,?version?=?version?+?1?where?id?=?1?and?version?=?1;//購買失敗CAS方式
CAS是一種樂觀鎖實現(xiàn)方式,顧名思義就是先比較后更新。在對一個數(shù)據(jù)進(jìn)行更新前,先持有對這個數(shù)據(jù)原有值的備份。比如,要將a=2更新為a=3,在進(jìn)行更新前會比較此刻a是否為2.如果是2,才會進(jìn)行更新操作。當(dāng)多個線程嘗試使用CAS同時更新一個變量時,只有一個線程能夠成功,其余都是失敗。失敗的線程不會被掛起,而是被告知這次競爭失敗,并且可以再次嘗試。
其代碼實現(xiàn)如下:
int compare_and_swap(int* reg, int oldval, int newval) { ATOMIC(); int old_reg_val = *reg; if (old_reg_val == oldval) *reg = newval; END_ATOMIC(); return old_reg_val;}因為其實現(xiàn)方式與version方式類似,此處不再贅述
使用場景
切不可說某種鎖優(yōu)于另外一種鎖,每種鎖都有其適合的場景,一般來說,樂觀鎖適用于讀比較多,寫比較少的場景,這樣,省去了系統(tǒng)開銷,加大了系統(tǒng)吞吐量;而悲觀鎖適用于寫比較多的場景,如果在多寫的場景下用樂觀鎖,這就使得應(yīng)用層有大量的retry操作。
優(yōu)缺點:
悲觀鎖
優(yōu)點:悲觀鎖利用數(shù)據(jù)庫中的鎖機(jī)制來實現(xiàn)數(shù)據(jù)變化的順序執(zhí)行,這是最有效的辦法
缺點:一個事務(wù)用悲觀鎖對數(shù)據(jù)加鎖后,其他事務(wù)將不能對加鎖的數(shù)據(jù)進(jìn)行除了查詢以外的所有操作,如果該事務(wù)執(zhí)行的時間很長,那么其他事務(wù)將一直等待,那是比影響我們系統(tǒng)的吞吐量。
樂觀鎖
樂觀鎖不在數(shù)據(jù)庫上加鎖,任何事務(wù)都可以對數(shù)據(jù)進(jìn)行操作,這樣就避免了使用悲觀鎖造成的吞吐量下降。
缺點:
1、樂觀鎖是通過人為實現(xiàn)的,僅僅適用于我們自己業(yè)務(wù)中,如果有外部事務(wù)插入,勢必引起異常
2、循環(huán)時間長開銷大
自選CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷
3、只能保證一個共享變量的原子操作。
總結(jié)
- 上一篇: CleanMyMac最新破解安装版
- 下一篇: 用于学术论文阅读外文学习的高效翻译工具—