MySQL間隙鎖解決幻讀問題
一、間隙鎖概念
- 當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù), 并請(qǐng)求共享或排他鎖時(shí),InnoDB 會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對(duì)于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)” ,InnoDB 也會(huì)對(duì)這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖
- 舉例來說, 假如 user 表中只有 101 條記錄, 其userid 的值分別是 1,2,…,100,101, 下面的 SQL: select * from user where userid > 100 for update;是一個(gè)范圍條件的檢索,InnoDB 不僅會(huì)對(duì)符合條件的 userid 值為 101 的記錄加鎖,也會(huì)對(duì)userid 大 于 101(但是這些記錄并不存在)的"間隙"加鎖,防止其它事務(wù)在表的末尾增加數(shù)據(jù)
- InnoDB 使用間隙鎖的目的,是為了防止幻讀,以滿足串行化隔離級(jí)別的要求 ,對(duì)于上面的例子,要是不使用間隙鎖,如果其他事務(wù)插入了 userid 大于 100 的任何記錄,那么本事務(wù)如果再次執(zhí)行上述語句,就會(huì)發(fā)生幻讀
InnoDB串行化隔離級(jí)別使用間隙鎖(gap lock)解決幻讀(事務(wù)并發(fā)情況下兩次查詢的數(shù)據(jù)量不同)問題
間隙鎖專用于串行化隔離級(jí)別,可解決幻讀問題,幻讀問題表現(xiàn)為:當(dāng)前事務(wù)沒做操作,前后兩次相同的查詢語句,顯示的數(shù)據(jù)量不一致

我們把事務(wù)2 select的指定的條件分為2類:范圍查詢、等值查詢
record lock:記錄鎖,就是行鎖
gap lock:間隙鎖,不包含記錄本身
next-key lock:record lock(記錄本身) + gap lock(不包括記錄本身)
二、測(cè)試間隙鎖范圍加鎖
設(shè)置事務(wù)為手動(dòng)提交,然后把隔離級(jí)別設(shè)置成串行化

查看表結(jié)構(gòu),id、age、name都有索引

場(chǎng)景1:用不可重復(fù)的主鍵id測(cè)試間隙鎖
范圍查詢

事務(wù)2的select操作只給三行數(shù)據(jù)加了共享鎖,為什么插入id為24的數(shù)據(jù)也不行呢?
這是因?yàn)樵诖谢綦x級(jí)別中,不僅僅是獲取了滿足條件的這3行的行鎖,而且把表記錄之間以及后邊空洞的地方也加上了間隙鎖

圖中紅色線的地方都上了next-key鎖,上鎖范圍(左開右閉)為:( 11 , 12 ] ∪ ( 12 , 22 ] ∪ ( 22 , 23 ] ∪ ( 23 , + ∞ ] (11,12]\cup(12,22]\cup(22,23]\cup(23,+\infty](11,12]∪(12,22]∪(22,23]∪(23,+∞]
上述select不僅僅獲取了12,22,23的共享行鎖(record-lock),還把間隙加了間隙鎖,其實(shí)就是給間隙加上共享鎖或者排他鎖,由于我們這里是select,所以是給間隙加上了共享鎖(我們select id>11還是可以的,不能update、insert、delete id>11的數(shù)據(jù))
也就是說,我們可以在id小于11的地方update/delete,加排它鎖。但是操作了id<=11的部分,不影響相同的select * from stu where id>11所獲取的數(shù)據(jù)量,這樣就能防止幻讀發(fā)生
串行化隔離級(jí)別通過給select的部分加間隙鎖,防止其他事務(wù)在加了間隙鎖的區(qū)間進(jìn)行增加或刪除數(shù)據(jù),就能防止幻讀
場(chǎng)景2:用可重復(fù)的age(有索引)測(cè)試間隙鎖
測(cè)試輔助索引樹上,間隙鎖的范圍
我們先查看一下表結(jié)構(gòu)、表數(shù)據(jù),然后回滾

開啟事務(wù)進(jìn)行測(cè)試

很明顯,由于age>20的區(qū)間都被事務(wù)1加上了間隙鎖(這里加的是共享鎖),所以事務(wù)2插入age=22和age=21都失敗了

幻讀就是同一事務(wù)兩次用相同的條件查詢數(shù)據(jù),下一次查出的數(shù)據(jù)量和上一次的數(shù)據(jù)量不一樣,就算事務(wù)1把a(bǔ)ge=20的數(shù)據(jù)插入表,事務(wù)2再用age>20查詢,得到的數(shù)據(jù)量也不會(huì)改變。
那事務(wù)1插入age=20的數(shù)據(jù)能否成功呢?

依然不能成功,這是因?yàn)槲覀儾迦氲臄?shù)據(jù)id是自增的,所以這條數(shù)據(jù)(age=20,id=24),位于輔助索引樹中(age=20,id=12)的右邊,由于(age=20,id=12)右邊都被上了鎖,(age=20,id=24)自然無法插入
也就是說,如果我們指定age=20,id合法且<12,則可插入

很顯然,事務(wù)1插入的age=18和age=19都不在事務(wù)2上鎖的范圍,所以可以插入
如果只是在輔助索引樹上查找,不回表,那么主鍵索引樹上不會(huì)加鎖


select id from stu where age>14后,輔助索引樹加鎖區(qū)間如下:


場(chǎng)景3:實(shí)際情況需要具體分析用的到底是行鎖還是表鎖

回滾,重新開啟事務(wù)

開始測(cè)試

我們發(fā)現(xiàn)事務(wù)1無論是插入age>18范圍內(nèi)的數(shù)據(jù),還是范圍外的數(shù)據(jù),都無法成功
這時(shí)我們就要分析了,這應(yīng)該沒有用到索引,因?yàn)槲覀冇盟饕?,過濾出的數(shù)據(jù)占了整張表的一大半,MySQL server沒使用索引。
沒有加行鎖,只能加表鎖(這時(shí)加的是共享鎖),所以事務(wù)1無論插入什么數(shù)據(jù)都不行

果然,沒有用到索引

age>20用到了索引,所以可以用行鎖
三、測(cè)試等值間隙鎖
查看表結(jié)構(gòu)和表數(shù)據(jù)

設(shè)置成手動(dòng)提交,設(shè)置串行化隔離級(jí)別。回滾,然后啟動(dòng)事務(wù)

測(cè)試不能重復(fù)的主鍵索引
此時(shí)事務(wù)2 select,由于是等值查詢,相當(dāng)于給這條數(shù)據(jù)加上了共享鎖

事務(wù)1現(xiàn)在插入新的數(shù)據(jù)是可以成功的,因?yàn)橹麈Iid不能重復(fù),我們不能再插入主鍵id=9的數(shù)據(jù)

在這種情況下,由于id=9的數(shù)據(jù)已經(jīng)存在,主鍵和唯一鍵是不能重復(fù)的,事務(wù)2進(jìn)行等值查詢時(shí),事務(wù)1插入一個(gè)新的數(shù)據(jù),不用擔(dān)心這條新插入的數(shù)據(jù)和查詢條件是一樣的,如果主鍵一樣,SQL語句執(zhí)行失敗,所以肯定能成功
測(cè)試能重復(fù)的輔助索引
回滾并重啟事務(wù)

事務(wù)2等值查詢,給age=18這行數(shù)據(jù)加上共享鎖(record-lock)


事務(wù)1插入age=18,這是不能允許的,否則事務(wù)2再查詢age=18就有兩條記錄了(幻讀)

奇怪的是,我們插入age=17、16、15都被阻塞了,而14、13成功了

這是因?yàn)椋瑸榱朔乐够米x,除了age=18這條數(shù)據(jù)加了共享鎖,其兩側(cè)也被加上了間隙鎖,因?yàn)樵谶@種情況下,插入(age=18,id=10)和(age=18,id=8)是會(huì)發(fā)生幻讀的,所以在一切會(huì)影響select * from user where age=18查詢結(jié)果的地方都加上了間隙鎖,但這也會(huì)導(dǎo)致一些本不影響查詢結(jié)果的語句也執(zhí)行失敗,比如插入(age=17,id=24)雖然不影響上述SQL執(zhí)行結(jié)果,由于在間隙鎖范圍內(nèi),依然無法插入
如果插入(age=15,id=1)就可以成功,根據(jù)輔助索引值相同,按照主鍵值升序排列,(age=15,id=1)應(yīng)該放在(age=15,id=23)前面,不在間隙鎖范圍內(nèi)

間隙鎖是給不存在的數(shù)據(jù)記錄的范圍加鎖:
對(duì)于輔助索引,若值允許重復(fù),在串行隔離級(jí)別中如果進(jìn)行等值查詢,InnoDB會(huì)給數(shù)據(jù)加上record-lock和gap-lock(防止別的事務(wù)插入索引值重復(fù)的數(shù)據(jù),造成幻讀)對(duì)于主鍵索引,或者唯一鍵索引,值不允許重復(fù),那只需要加行鎖就夠了,不需要再加間隙鎖(對(duì)于唯一鍵索引,不可能發(fā)生插入索引值重復(fù)的數(shù)據(jù))
串行化隔離級(jí)別通過排它鎖和共享鎖解決臟讀、不可重復(fù)讀(兩次查詢的數(shù)據(jù)內(nèi)容不同),通過間隙鎖解決幻讀(兩次查詢的數(shù)據(jù)量不同)
到此這篇關(guān)于MySQL間隙鎖解決幻讀問題的文章就介紹到這了,更多相關(guān)MySQL間隙鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
教你如何讓spark?sql寫mysql的時(shí)候支持update操作
spark提供了一個(gè)枚舉類,用來支撐對(duì)接數(shù)據(jù)源的操作模式,本文重點(diǎn)給大家介紹如何讓spark?sql寫mysql的時(shí)候支持update操作,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-02-02
MySQL數(shù)據(jù)庫健康檢查從腳本到全面巡檢的完整方案
在數(shù)據(jù)庫運(yùn)維工作中,定期對(duì)MySQL數(shù)據(jù)庫進(jìn)行健康檢查是保證系統(tǒng)穩(wěn)定運(yùn)行的重要環(huán)節(jié),一個(gè)完善的數(shù)據(jù)庫巡檢方案可以幫助DBA及時(shí)發(fā)現(xiàn)潛在問題,優(yōu)化性能,預(yù)防故障發(fā)生,本文將基于多個(gè)優(yōu)秀的MySQL巡檢腳本實(shí)現(xiàn),整理出一套完整的MySQL健康檢查方案,需要的朋友可以參考下2025-11-11
Mysql系列SQL查詢語句書寫順序及執(zhí)行順序詳解
這篇文章主要為大家介紹了Mysql系列SQL查詢語句的書寫順序及執(zhí)行順序示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-10-10
mysql?sum(if())和count(if())的用法說明
這篇文章主要介紹了mysql?sum(if())和count(if())的用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
mysql數(shù)據(jù)庫優(yōu)化必會(huì)的幾個(gè)參數(shù)中文解釋
對(duì)于自己配置mysql數(shù)據(jù)庫的朋友,需要注意的幾點(diǎn),下面都是英文的解釋,比較易懂方便和我一樣需要優(yōu)化配置mysql的朋友2008-09-09
MySql 8.0.11-Winxp64(免安裝版)配置教程
這篇文章主要介紹了MySql 8.0.11-Winxp64(免安裝版)配置教程,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2018-05-05

