本文中,我們?cè)敿?xì)介紹MySQL InnoDB存儲(chǔ)引擎各種不同類型的鎖,以及不同SQL語(yǔ)句分別會(huì)加什么樣的鎖。
閱讀提示
1、本文所參考的MySQL文檔版本是8.0,做實(shí)驗(yàn)的MySQL版本是8.0.13
2、本文主要參考了MySQL官方文檔 InnoDB鎖定和事務(wù)機(jī)制
3、本文還參考了何登成的 MySQL加鎖處理分析、一個(gè)最不可思議的MySQL死鎖分析 以及阿里云RDS-數(shù)據(jù)庫(kù)內(nèi)核組的 常用SQL語(yǔ)句的MDL加鎖源碼分析
4、MySQL是插件式的表存儲(chǔ)引擎,數(shù)據(jù)庫(kù)的鎖是和存儲(chǔ)引擎相關(guān)的,本文討論的鎖都是InnoDB存儲(chǔ)引擎的鎖
文章正文開(kāi)始
“加什么樣的鎖”與以下因素相關(guān)
1、當(dāng)前事務(wù)的隔離級(jí)別
2、SQL是一致性非鎖定讀(consistent nonlocking read)還是DML(INSERT/UPDATE/DELETE)或鎖定讀(locking read)
3、SQL執(zhí)行時(shí)是否使用了索引,所使用索引的類型(主鍵索引,輔助索引、唯一索引)
我們先分別介紹這幾個(gè)因素
一、隔離級(jí)別(isolation level)
數(shù)據(jù)庫(kù)事務(wù)需要滿足ACID原則,“I”即隔離性,它要求兩個(gè)事務(wù)互不影響,不能看到對(duì)方尚未提交的數(shù)據(jù)。數(shù)據(jù)庫(kù)有4種隔離級(jí)別(isolation level),按著隔離性從弱到強(qiáng)(相應(yīng)的,性能和并發(fā)性從強(qiáng)到弱)分別是
1、Read Uncommitted。下面簡(jiǎn)稱RU
2、Read Committed。下面簡(jiǎn)稱RC
3、Repeatable Read(MySQL的默認(rèn)隔離級(jí)別)。下面簡(jiǎn)稱RR
4、Serializable
“I”即隔離性正是通過(guò)鎖機(jī)制來(lái)實(shí)現(xiàn)的。提到鎖就會(huì)涉及到死鎖,需要明確的是死鎖的可能性并不受隔離級(jí)別的影響,因?yàn)楦綦x級(jí)別改變的是讀操作的行為,而死鎖是由于寫操作產(chǎn)生的。
-- 查看事務(wù)的 全局和session 隔離級(jí)別( MySQL 5.7.19及之前使用tx_isolation)
select @@global.transaction_isolation, @@session.transaction_isolation;
-- 設(shè)置 全局 事務(wù)隔離級(jí)別為repeatable read
set global transaction isolation level repeatable read
-- 設(shè)置 當(dāng)前session 事務(wù)隔離級(jí)別為read uncommitted
set session transaction isolation level read uncommitted
事務(wù)隔離級(jí)別設(shè)置和查看的詳細(xì)語(yǔ)法請(qǐng)見(jiàn):
https://dev.mysql.com/doc/refman/8.0/en/set-transaction.html
二、一致性非鎖定讀和鎖定讀
InnoDB有兩種不同的SELECT,即普通SELECT 和 鎖定讀SELECT。鎖定讀SELECT 又有兩種,即SELECT ... FOR SHARE 和 SELECT ... FOR UPDATE;鎖定讀SELECT 之外的則是 普通SELECT 。
不同的SELECT是否都需要加鎖呢?
1、普通SELECT 時(shí)使用一致性非鎖定讀,不加鎖;
2、鎖定讀SELECT 使用鎖定讀,加鎖;
3、此外,DML(INSERT/UPDATE/DELETE)時(shí),需要先查詢表中的記錄,此時(shí)也使用鎖定讀,加鎖;
FOR SHARE 語(yǔ)法是 MySQL 8.0 時(shí)加入的,F(xiàn)OR SHARE 和 LOCK IN SHARE MODE 是等價(jià)的,但,F(xiàn)OR SHARE 用于替代 LOCK IN SHARE MODE,不過(guò),為了向后兼容,LOCK IN SHARE MODE依然可用。
1、一致性非鎖定讀(consistent nonlocking read)
InnoDB采用多版本并發(fā)控制(MVCC, multiversion concurrency control)來(lái)增加讀操作的并發(fā)性。MVCC是指,InnoDB使用基于時(shí)間點(diǎn)的快照來(lái)獲取查詢結(jié)果,讀取時(shí)在訪問(wèn)的表上不設(shè)置任何鎖,因此,在事務(wù)T1讀取的同一時(shí)刻,事務(wù)T2可以自由的修改事務(wù)T1所讀取的數(shù)據(jù)。這種讀操作被稱為一致性非鎖定讀。這里的讀操作就是普通SELECT。
隔離級(jí)別為RU和Serializable時(shí)不需要MVCC,因此,只有RC和RR時(shí),才存在MVCC,才存在一致性非鎖定讀。
一致性非鎖定讀在兩種隔離級(jí)別RC和RR時(shí),是否有什么不同呢?是的,兩種隔離級(jí)別下,拍得快照的時(shí)間點(diǎn)不同
1、RC時(shí),同一個(gè)事務(wù)內(nèi)的每一個(gè)一致性讀總是設(shè)置和讀取它自己的最新快照。也就是說(shuō),每次讀取時(shí),都再重新拍得一個(gè)最新的快照(所以,RC時(shí)總是可以讀取到最新提交的數(shù)據(jù))。
2、RR時(shí),同一個(gè)事務(wù)內(nèi)的所有的一致性讀 總是讀取同一個(gè)快照,此快照是執(zhí)行該事務(wù)的第一個(gè)一致性讀時(shí)所拍得的。
2、鎖定讀(locking read)
如果你先查詢數(shù)據(jù),然后,在同一個(gè)事務(wù)內(nèi) 插入/更新 相關(guān)數(shù)據(jù),普通的SELECT語(yǔ)句是不能給你足夠的保護(hù)的。其他事務(wù)可以 更新/刪除 你剛剛查出的數(shù)據(jù)行。InnoDB提供兩種鎖定讀,即:SELECT ... FOR SHARE 和 SELECT ... FOR UPDATE。它倆都能提供額外的安全性。
這兩種鎖定讀在搜索時(shí)所遇到的(注意:不是最終結(jié)果集中的)每一條索引記錄(index record)上設(shè)置排它鎖或共享鎖。此外,如果當(dāng)前隔離級(jí)別是RR,它還會(huì)在每個(gè)索引記錄前面的間隙上設(shè)置排它的或共享的gap lock(排它的和共享的gap lock沒(méi)有任何區(qū)別,二者等價(jià))。
看完背景介紹,我們?cè)賮?lái)看一下InnoDB提供的各種鎖。
三、InnoDB提供的8種不同類型的鎖
InnoDB一共有8種鎖類型,其中,意向鎖(Intention Locks)和自增鎖(AUTO-INC Locks)是表級(jí)鎖,剩余全部都是行級(jí)鎖。此外,共享鎖或排它鎖(Shared and Exclusive Locks)盡管也作為8種鎖類型之一,它卻并不是具體的鎖,它是鎖的模式,用來(lái)“修飾”其他各種類型的鎖。
MySQL5.7及之前,可以通過(guò)information_schema.innodb_locks查看事務(wù)的鎖情況,但,只能看到阻塞事務(wù)的鎖;如果事務(wù)并未被阻塞,則在該表中看不到該事務(wù)的鎖情況。
MySQL8.0刪除了information_schema.innodb_locks,添加了performance_schema.data_locks,可以通過(guò)performance_schema.data_locks查看事務(wù)的鎖情況,和MySQL5.7及之前不同,performance_schema.data_locks不但可以看到阻塞該事務(wù)的鎖,還可以看到該事務(wù)所持有的鎖,也就是說(shuō)即使事務(wù)并未被阻塞,依然可以看到事務(wù)所持有的鎖(不過(guò),正如文中最后一段所說(shuō),performance_schema.data_locks并不總是能看到全部的鎖)。表名的變化其實(shí)還反映了8.0的performance_schema.data_locks更為通用了,即使你使用InnoDB之外的存儲(chǔ)引擎,你依然可以從performance_schema.data_locks看到事務(wù)的鎖情況。
performance_schema.data_locks的列LOCK_MODE表明了鎖的類型,下面在介紹各種鎖時(shí),我們同時(shí)指出鎖的LOCK_MODE。
1、共享鎖或排它鎖(Shared and Exclusive Locks)
它并不是一種鎖的類型,而是其他各種鎖的模式,每種鎖都有shard或exclusive兩種模式。
當(dāng)我們說(shuō)到共享鎖(S鎖)或排它鎖(X鎖)時(shí),一般是指行上的共享鎖或者行上的排它鎖。需要注意的是,表鎖也存在共享鎖和排它鎖,即表上的S鎖和表上的X鎖,表上的鎖除了這兩種之外,還包括下面將會(huì)提到的意向共享鎖(Shard Intention Locks)即IS鎖、意向排它鎖(Exclusive Intention Locks)即IX鎖。表上的鎖,除了這四種之外,還有其他類型的鎖,這些鎖都是在訪問(wèn)表的元信息時(shí)會(huì)用到的(create table/alter table/drop table等),本文不討論這些鎖,詳細(xì)可見(jiàn):常用SQL語(yǔ)句的MDL加鎖源碼分析。
數(shù)據(jù)行r上共享鎖(S鎖)和排它鎖(X鎖)的兼容性如下:
假設(shè)T1持有數(shù)據(jù)行r上的S鎖,則當(dāng)T2請(qǐng)求r上的鎖時(shí):
1、T2請(qǐng)求r上的S鎖,則,T2立即獲得S鎖。T1和T2同時(shí)都持有r上的S鎖。
2、T2請(qǐng)求r上的X鎖,則,T2無(wú)法獲得X鎖。T2必須要等待直到T1釋放r上的S鎖。
假設(shè)T1持有r上的X鎖,則當(dāng)T2請(qǐng)求r上的鎖時(shí):
T2請(qǐng)求r上的任何類型的鎖時(shí),T2都無(wú)法獲得鎖,此時(shí),T2必須要等待直到T1釋放r上的X鎖
2、意向鎖(Intention Locks)
表鎖。含義是已經(jīng)持有了表鎖,稍候?qū)@取該表上某個(gè)/些行的行鎖。有shard或exclusive兩種模式。
LOCK_MODE分別是:IS或IX。
意向鎖用來(lái)鎖定層級(jí)數(shù)據(jù)結(jié)構(gòu),獲取子層級(jí)的鎖之前,必須先獲取到父層級(jí)的鎖??梢赃@么看InnoB的層級(jí)結(jié)構(gòu):InnoDB所有數(shù)據(jù)是schema的集合,schema是表的集合,表是行的集合。意向鎖就是獲取子層級(jí)(數(shù)據(jù)行)的鎖之前,需要首先獲取到父層級(jí)(表)的鎖。
意向鎖的目的是告知其他事務(wù),某事務(wù)已經(jīng)鎖定了或即將鎖定某個(gè)/些數(shù)據(jù)行。事務(wù)在獲取行鎖之前,首先要獲取到意向鎖,即:
1、事務(wù)在獲取行上的S鎖之前,事務(wù)必須首先獲取 表上的 IS鎖或表上的更強(qiáng)的鎖。
2、事務(wù)在獲取行上的X鎖之前,事務(wù)必須首先獲取 表上的 IX鎖。
事務(wù)請(qǐng)求鎖時(shí),如果所請(qǐng)求的鎖 與 已存在的鎖兼容,則該事務(wù) 可以成功獲得 所請(qǐng)求的鎖;如果所請(qǐng)求的鎖 與 已存在的鎖沖突,則該事務(wù) 無(wú)法獲得 所請(qǐng)求的鎖。
表級(jí)鎖(table-level lock)的兼容性矩陣如下:
對(duì)于上面的兼容性矩陣,一定注意兩點(diǎn):
1、在上面的兼容性矩陣中,S是表的(不是行的)共享鎖,X是表的(不是行的)排它鎖。
2、意向鎖IS和IX 和任何行鎖 都兼容(即:和行的X鎖或行的S鎖都兼容)。
所以,意向鎖只會(huì)阻塞 全表請(qǐng)求(例如:LOCK TABLES ... WRITE),不會(huì)阻塞其他任何東西。因?yàn)長(zhǎng)OCK TABLES ... WRITE需要設(shè)置X表鎖,這會(huì)被意向鎖IS或IX所阻塞。
InnoDB允許表鎖和行鎖共存,使用意向鎖來(lái)支持多粒度鎖(multiple granularity locking)。意向鎖如何支持多粒度鎖呢,我們舉例如下
T1: SELECT * FROM t1 WHERE i=1 FOR UPDATE;
T2: LOCK TABLE t1 WRITE;
T1執(zhí)行時(shí),需要獲取i=1的行的X鎖,但,T1獲取行鎖前,T1必須先要獲取t1表的IX鎖,不存在沖突,于是T1成功獲得了t1表的IX鎖,然后,又成功獲得了i=1的行的X鎖;T2執(zhí)行時(shí),需要獲取t1表的X鎖,但,T2發(fā)現(xiàn),t1表上已經(jīng)被設(shè)置了IX鎖,因此,T2被阻塞(因?yàn)楸淼腦鎖和表的IX鎖不兼容)。
假設(shè)不存在意向鎖,則:
T1執(zhí)行時(shí),需要獲取i=1的行的X鎖(不需要獲取t1表的意向鎖了);T2執(zhí)行時(shí),需要獲取t1表的X鎖,T2能否獲取到T1表的X鎖呢?T2無(wú)法立即知道,T2不得不遍歷表t1的每一個(gè)數(shù)據(jù)行以檢查,是否某個(gè)行上已存在的鎖和自己即將設(shè)置的t1表的X鎖沖突,這種的判斷方法效率實(shí)在不高,因?yàn)樾枰闅v整個(gè)表。
所以,使用意向鎖,實(shí)現(xiàn)了“表鎖是否沖突”的快速判斷。意向鎖就是協(xié)調(diào)行鎖和表鎖之間的關(guān)系的,或者也可以說(shuō),意向鎖是協(xié)調(diào)表上面的讀寫鎖和行上面的讀寫鎖(也就是不同粒度的鎖)之間的關(guān)系的。
3、索引記錄鎖(Record Locks)
也就是所謂的行鎖,鎖定的是索引記錄。行鎖就是索引記錄鎖,所謂的“鎖定某個(gè)行”或“在某個(gè)行上設(shè)置鎖”,其實(shí)就是在某個(gè)索引的特定索引記錄(或稱索引條目、索引項(xiàng)、索引入口)上設(shè)置鎖。有shard或exclusive兩種模式。
LOCK_MODE分別是:S,REC_NOT_GAP或X,REC_NOT_GAP。
行鎖就是索引記錄鎖,索引記錄鎖總是鎖定索引記錄,即使表上并未定義索引。表未定義索引時(shí),InnoDB自動(dòng)創(chuàng)建隱藏的聚集索引(索引名字是GEN_CLUST_INDEX),使用該索引執(zhí)行record lock。
4、間隙鎖(Gap Locks)
索引記錄之間的間隙上的鎖,鎖定尚未存在的記錄,即索引記錄之間的間隙。有shard或exclusive兩種模式,但,兩種模式?jīng)]有任何區(qū)別,二者等價(jià)。
LOCK_MODE分別是:S,GAP或X,GAP。
gap lock可以共存(co-exist)。事務(wù)T1持有某個(gè)間隙上的gap lock 并不能阻止 事務(wù)T2同時(shí)持有 同一個(gè)間隙上的gap lock。shared gap lock和exclusive gap lock并沒(méi)有任何的不同,它倆并不沖突,它倆執(zhí)行同樣的功能。
gap lock鎖住的間隙可以是第一個(gè)索引記錄前面的間隙,或相鄰兩條索引記錄之間的間隙,或最后一個(gè)索引記錄后面的間隙。
索引是B+樹(shù)組織的,因此索引是從小到大按序排列的,在索引記錄上查找給定記錄時(shí),InnoDB會(huì)在第一個(gè)不滿足查詢條件的記錄上加gap lock,防止新的滿足條件的記錄插入。
上圖演示了:InnoDB在索引上掃描時(shí),找到了c2=11的記錄,然后,InnoDB接著掃描,它發(fā)現(xiàn)下一條記錄是c2=18,不滿足條件,InnoDB遇到了第一個(gè)不滿足查詢條件的記錄18,于是InnoDB在18上設(shè)置gap lock,此gap lock鎖定了區(qū)間(11, 18)。
為什么需要gap lock呢?gap lock存在的唯一目的就是阻止其他事務(wù)向gap中插入數(shù)據(jù)行,它用于在隔離級(jí)別為RR時(shí),阻止幻影行(phantom row)的產(chǎn)生;隔離級(jí)別為RC時(shí),搜索和索引掃描時(shí),gap lock是被禁用的,只在 外鍵約束檢查 和 重復(fù)key檢查時(shí)gap lock才有效,正是因?yàn)榇耍琑C時(shí)會(huì)有幻影行問(wèn)題。
gap lock是如何阻止其他事務(wù)向gap中插入數(shù)據(jù)行的呢?看下圖
索引是B+樹(shù)組織的,因此索引是從小到大按序排列的,如果要插入10,那么能插入的位置只能是上圖中標(biāo)紅的區(qū)間。在10和10之間插入時(shí),我們就認(rèn)為是插入在最后面的10的后面。如果封鎖了標(biāo)紅的區(qū)間,那么其他事務(wù)就無(wú)法再插入10啦。
問(wèn)題一:當(dāng)T2要插入 10時(shí),上圖哪些地方允許插入(注意:索引是有序的哦)?
答:(8, 10)和(10,11)。在10和10之間插入,我們就認(rèn)為是插入在最后的10后面。
只要封鎖住圖中標(biāo)紅的區(qū)間,T2就無(wú)法再插入10啦。上面這兩個(gè)區(qū)間有什么特點(diǎn)嗎?對(duì),這兩個(gè)區(qū)間就是:滿足條件的每一條記錄前面的間隙,及,最后一條不滿足條件的記錄前面的間隙。InnoDB使用下一個(gè)鍵鎖(Next-Key Locks)或間隙鎖(Gap Locks)來(lái)封鎖這種區(qū)間。
問(wèn)題二:gap lock是用來(lái)阻塞插入新數(shù)據(jù)行的,那么,T2, insert into g values('z', 9) 會(huì)被阻塞嗎?插入('z', 8),('z', 10),('z', 11)呢?
答:上圖中,T1的update設(shè)置的gap lock是 (8, 10)和(10,11),而,insert intention lock的范圍是(插入值, 向下的一個(gè)索引值)。insert intention lock的詳細(xì)介紹請(qǐng)見(jiàn)下面的6. 插入意向鎖(Insert Intention Locks)。
于是,對(duì)于上面這些插入值,得到的insert intention lock如下:
插入 ('z', 8)時(shí),insert intention lock 是 (8, 10) -- 沖突,與gap lock (8, 10)重疊了
插入 ('z', 9)時(shí),insert intention lock 是 (9, 10) -- 沖突,與gap lock (8, 10)重疊了
插入 ('z', 10)時(shí),insert intention lock 是 (10, 11) -- 沖突,與gap lock (10, 11)重疊了
插入 ('z', 11)時(shí),insert intention lock 是 (11, 15) -- 不沖突
事實(shí)是不是這樣呢,看下圖
是的,和我們分析的一致,為了看的更清楚,我們把結(jié)果列成圖表如下
問(wèn)題三:“gap是解決phantom row問(wèn)題的”,插入會(huì)導(dǎo)致phantom row,但更新也一樣也會(huì)產(chǎn)生phantom row啊。
例如,上圖的T1和T2,T1把所有i=8的行更新為108,T2把i=15的行更新為8,如果T2不被阻塞,T1的WHERE條件豈不是多出了一行,即:T1出現(xiàn)了phantom row?
答:nice question。我們自己來(lái)分析下T1和T2分別加了哪些鎖
T1加的鎖:idx_i上的next-key lock (5, 8],PRIMARY上的'b',以及idx_i上的gap lock (8,10)
T2加的鎖:idx_i上的next-key lock (11, 15],PRIMARY上的'f',以及idx_i上的gap lock (15,108),最后這個(gè)gap lock是因?yàn)門1在idx_i上加了新值108
根據(jù)上面的分析,T1和T2的鎖并沒(méi)有重疊,即我們分析的結(jié)果是:T2不會(huì)被阻塞。
但,上圖清楚的表明T2確實(shí)被阻塞了,原因竟然是:T2 insert intention lock和T1 gap lock(8, 10)沖突了。很奇怪,T2是更新語(yǔ)句,為什么會(huì)有insert intention lock呢?
我不知道確切的原因,因?yàn)槲覜](méi)找到文檔說(shuō)這事。根據(jù)我的推斷,update ... set 成功找到結(jié)果集然后執(zhí)行更新時(shí),在即將被更新進(jìn)入行的新值上設(shè)置了insert intention lock(如果找不到結(jié)果集,則就不存在insert intention lock啦),因此,T2在idx_i上的新值8上設(shè)置了insert intention lock(8, 10)。最終,T2 insert intention lock(8, 10) 與 T1 gap lock(8, 10)沖突啦,T2被阻塞。
因此,update ... set 成功找到結(jié)果集時(shí),會(huì)在即將被更新進(jìn)入行的新值上設(shè)置 index record lock 以及 insert intention lock。如前所述,insert intention lock的范圍是(插入值,下一個(gè)值),如果T2是 update g set i=9 where i=15; 那么update ... set 所設(shè)置的新值是9,則T2 insert intention lock就是(9, 10)啦,它依然會(huì)和 T1 gap lock(8, 10)沖突,是這樣嗎?確實(shí)是的,感興趣的同學(xué)可以試試。
5、下一個(gè)鍵鎖(Next-Key Locks)
next-key lock 是 (索引記錄上的索引記錄鎖) + (該索引記錄前面的間隙上的鎖) 二者的合體,它鎖定索引記錄以及該索引記錄前面的間隙。有shard或exclusive兩種模式。
LOCK_MODE分別是:S或X。
當(dāng)InnoDB 搜索或掃描索引時(shí),InnoDB在它遇到的索引記錄上所設(shè)置的鎖就是next-key lock,它會(huì)鎖定索引記錄本身以及該索引記錄前面的gap("gap" immediately before that index record)。即:如果事務(wù)T1 在索引記錄r 上有一個(gè)next-key lock,則T2無(wú)法在 緊靠著r 前面的那個(gè)間隙中 插入新的索引記錄(gap immediately before r in the index order)。
next-key lock還會(huì)加在“supremum pseudo-record”上,什么是supremum pseudo-record呢?它是索引中的偽記錄(pseudo-record),代表此索引中可能存在的最大值,設(shè)置在supremum pseudo-record上的next-key lock鎖定了“此索引中可能存在的最大值”,以及 這個(gè)值前面的間隙,“此索引中可能存在的最大值”在索引中是不存在的,因此,該next-key lock實(shí)際上鎖定了“此索引中可能存在的最大值”前面的間隙,也就是此索引中當(dāng)前實(shí)際存在的最大值后面的間隙。例如,下圖中,supremum pseudo-record上的next-key lock鎖定了區(qū)間(18, 正無(wú)窮),正是此next-key lock阻止其他事務(wù)插入例如19, 100等更大的值。
supremum pseudo-record上的next-key lock鎖定了“比索引中當(dāng)前實(shí)際存在的最大值還要大”的那個(gè)間隙,“比大還大”,“bigger than bigger”
6、插入意向鎖(Insert Intention Locks)
一種特殊的gap lock。INSERT操作插入成功后,會(huì)在新插入的行上設(shè)置index record lock,但,在插入行之前,INSERT操作會(huì)首先在索引記錄之間的間隙上設(shè)置insert intention lock,該鎖的范圍是(插入值, 向下的一個(gè)索引值)。有shard或exclusive兩種模式,但,兩種模式?jīng)]有任何區(qū)別,二者等價(jià)。
LOCK_MODE分別是:S,GAP,INSERT_INTENTION或X,GAP,INSERT_INTENTION。
insert intention lock發(fā)出按此方式進(jìn)行插入的意圖:多個(gè)事務(wù)向同一個(gè)index gap并發(fā)進(jìn)行插入時(shí),多個(gè)事務(wù)無(wú)需相互等待。
假設(shè)已存在值為4和7的索引記錄,事務(wù)T1和T2各自嘗試插入索引值5和6,在得到被插入行上的index record lock前,倆事務(wù)都首先設(shè)置insert intention lock,于是,T1 insert intention lock (5, 7),T2 insert intention lock (6, 7),盡管這兩個(gè)insert intention lock重疊了,T1和T2并不互相阻塞。
如果gap lock或next-key lock 與 insert intention lock 的范圍重疊了,則gap lock或next-key lock會(huì)阻塞insert intention lock。隔離級(jí)別為RR時(shí)正是利用此特性來(lái)解決phantom row問(wèn)題;盡管insert intention lock也是一種特殊的gap lock,但它和普通的gap lock不同,insert intention lock相互不會(huì)阻塞,這極大的提供了插入時(shí)的并發(fā)性??偨Y(jié)如下:
1、gap lock會(huì)阻塞insert intention lock。事實(shí)上,gap lock的存在只是為了阻塞insert intention lock
2、gap lock相互不會(huì)阻塞
3、insert intention lock相互不會(huì)阻塞
4、insert intention lock也不會(huì)阻塞gap lock
INSERT插入行之前,首先在索引記錄之間的間隙上設(shè)置insert intention lock,操作插入成功后,會(huì)在新插入的行上設(shè)置index record lock。
我們用下面三圖來(lái)說(shuō)明insert intention lock的范圍和特性
上圖演示了:T1設(shè)置了gap lock(13, 18),T2設(shè)置了insert intention lock(16, 18),兩個(gè)鎖的范圍重疊了,于是T1 gap lock(13, 18)阻塞了T2 insert intention lock(16, 18)。
上圖演示了:T1設(shè)置了insert intention lock(13, 18)、index record lock 13;T2設(shè)置了gap lock(17, 18)。盡管T1 insert intention lock(13, 18) 和 T2 gap lock(17, 18)重疊了,但,T2并未被阻塞。因?yàn)?insert intention lock 并不阻塞 gap lock。
上圖演示了:T1設(shè)置了insert intention lock(11, 18)、index record lock 11;T2設(shè)置了next-key lock(5, 11]、PRIMARY上的index record lock 'b'、gap lock(11, 18)。此時(shí):T1 index record lock 11 和 T2 next-key lock(5, 11]沖突了,因此,T2被阻塞。
7、自增鎖(AUTO-INC Locks)
表鎖。向帶有AUTO_INCREMENT列 的表時(shí)插入數(shù)據(jù)行時(shí),事務(wù)需要首先獲取到該表的AUTO-INC表級(jí)鎖,以便可以生成連續(xù)的自增值。插入語(yǔ)句開(kāi)始時(shí)請(qǐng)求該鎖,插入語(yǔ)句結(jié)束后釋放該鎖(注意:是語(yǔ)句結(jié)束后,而不是事務(wù)結(jié)束后)。
你可能會(huì)想,日常開(kāi)發(fā)中,我們所有表都使用AUTO_INCREMENT作主鍵,所以會(huì)非常頻繁的使用到該鎖。不過(guò),事情可能并不像你想的那樣。在介紹AUTO-INC表級(jí)鎖之前,我們先來(lái)看下和它密切相關(guān)的SQL語(yǔ)句以及系統(tǒng)變量innodb_autoinc_lock_mode
INSERT-like語(yǔ)句
1、insert
2、insert ... select
3、replace
4、replace ... select
5、load data
外加,simple-inserts, bulk-inserts, mixed-mode-inserts
simple-inserts
待插入記錄的條數(shù),提前就可以確定(語(yǔ)句初始被處理時(shí)就可以提前確定)因此所需要的自增值的個(gè)數(shù)也就可以提前被確定。
包括:不帶嵌入子查詢的 單行或多行的insert, replace。不過(guò),insert ... on duplicate key update不是
bulk-inserts
待插入記錄的條數(shù),不能提前確定,因此所需要的自增值的個(gè)數(shù) 也就無(wú)法提前確定
包括:insert ... select, replace ... select, load data
在這種情況下,InnoDB只能每次一行的分配自增值。每當(dāng)一個(gè)數(shù)據(jù)行被處理時(shí),InnoDB為該行AUTO_INCREMENT列分配一個(gè)自增值
mixed-mode-inserts
也是simple-inserts語(yǔ)句,但是指定了某些(非全部)自增列的值。也就是說(shuō),待插入記錄的條數(shù)提前能知道,但,指定了部分的自增列的值。
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
INSERT ... ON DUPLICATE KEY UPDATE也是mixed-mode,最壞情況下,它就是INSERT緊跟著一個(gè)UPDATE,此時(shí),為AUTO_INCREMENT列所分配的值在UPDATE階段可能用到,也可能用不到。
再看一下系統(tǒng)變量innodb_autoinc_lock_mode,它有三個(gè)候選值0,1,和2
8.0.3之前,默認(rèn)值是1,即“連續(xù)性的鎖定模式(consecutive lock mode)”;8.0.3及之后默認(rèn)值是2,即“交織性鎖定模式(interleaved lock mode)”
a. 當(dāng)innodb_autoinc_lock_mode=0時(shí),INSERT-like語(yǔ)句都需要獲取到AUTO-INC表級(jí)鎖;
b. 當(dāng)innodb_autoinc_lock_mode=1時(shí),如果插入行的條數(shù)可以提前確定,則無(wú)需獲得AUTO-INC表級(jí)鎖;如果插入行的條數(shù)無(wú)法提前確定,則就需要獲取AUTO-INC表級(jí)鎖。因此,simple-inserts和mixed-mode inserts都無(wú)需AUTO-INC表級(jí)鎖,此時(shí),使用輕量級(jí)的mutex來(lái)互斥獲得自增值;bulk-inserts需要獲取到AUTO-INC表級(jí)鎖;
c. 當(dāng)innodb_autoinc_lock_mode=2時(shí),完全不再使用AUTO-INC表級(jí)鎖;
我們生產(chǎn)數(shù)據(jù)庫(kù)版本是5.6.23-72.1,innodb_autoinc_lock_mode=1,而且,我們?nèi)粘i_(kāi)發(fā)中用到大都是simple-inserts,此時(shí)根本就不使用AUTO-INC表級(jí)鎖,所以,AUTO-INC表級(jí)鎖用到的并不多哦。
LOCK_MODE:AUTO-INC表級(jí)鎖用到的并不多,且,AUTO-INC鎖是在語(yǔ)句結(jié)束后被釋放,較難在performance_schema.data_locks中查看到,因此,沒(méi)有進(jìn)行捕獲。感興趣的同學(xué)可以使用INSERT ... SELECT捕獲試試。
8、空間索引(Predicate Locks for Spatial Indexes)
我們平時(shí)很少用到MySQL的空間索引。所以,本文忽略此類型的鎖
到此為止,MySQL InnoDB 8種類型的鎖我們就介紹完了。我們以一個(gè)例子結(jié)束8種類型的介紹。
T1先執(zhí)行,事務(wù)ID是8428;T2后執(zhí)行,事務(wù)ID是8429
上圖演示了:
1、任何事務(wù),在鎖定行之前,都需要先加表級(jí)鎖intention lock,即:第三行的IX和第一行的IX。
2、idx_c是輔助索引,InnoDB掃描idx_c時(shí)遇到了c=222,于是,在idx_c上加了next-key lock,即:第四行的X。next-key lock就是 index record lock+gap lock,于是此next-key lock鎖定了idx_c上值為222的索引記錄,以及222前面的間隙,也就是間隙(22, 222)。
3、idx_c是輔助索引,在主鍵索引之外的任何索引上加index record lock時(shí),都需要在該行的主鍵索引上再加index record lock,于是,又在PRIMARY上添加了index record lock,即:第五行的X,REC_NOT_GAP。
4、InnoDB掃描完c=222后,又掃描到了c=2222,這是idx_c上,第一個(gè)不滿足索引掃描條件的索引記錄,于是InnoDB在c=2222上加gap lock,c=2222上的gap lock鎖定的范圍是“idx_c上2222前面的間隙”,這本應(yīng)該是(222, 2222),但,T1即將在idx_c上插入c=224,于是,c=2222上的gap lock鎖定的范圍是(224, 2222)。即:第六行的X,GAP。
5、InnoDB即將在idx_c上插入c=224,224也是不滿足c=222的,于是InnoDB在c=224上加gap lock,該gap lock鎖定了224前面的間隙,也就是(222, 224),即,第七行的X,GAP。
6、T2執(zhí)行INSERT成功后,會(huì)在新插入行的加index record lock,但,T2在插入之前,首先要作的是得到表級(jí)鎖intention lock以及設(shè)置表的每個(gè)索引的insert intention lock,該鎖的范圍是(插入值, 向下的一個(gè)索引值),于是,在設(shè)置idx_c上的insert intention lock范圍就是(226, 2222),這個(gè)范圍和事務(wù)T1第六行g(shù)ap lock范圍(224, 2222)重疊。于是,事務(wù)T2被阻塞了,T2必須等待,直到T1釋放第六行的gap lock。
performance_schema.data_locks表中并不能看到T2的全部鎖,比如,T2也得在iux_b上設(shè)置insert intention lock,但,performance_schema.data_locks中并沒(méi)有這個(gè)鎖。關(guān)于performance_schema.data_locks中顯示了哪些鎖,請(qǐng)見(jiàn)本文最后一段。
把這些鎖及其范圍列出來(lái)如下圖所示
四、不同的SQL加了什么樣的鎖?
OK,我們已經(jīng)了解了InnoDB各種不同類型的鎖,那么,不同SQL語(yǔ)句各加了什么樣的鎖呢
我們用最樸素的想法來(lái)思考一下,用鎖作什么呢?鎖要作的就是達(dá)到事務(wù)隔離的目的,即:兩個(gè)并發(fā)執(zhí)行的事務(wù)T1和T2,如果T1正在修改某些行,那么,T2要并發(fā) 讀取/修改/插入 滿足T1查詢條件的行時(shí),T2就必須被阻塞,這是鎖存在的根本原因。index record lock, gap lock, next-key lock都是實(shí)現(xiàn)手段,這些手段使得鎖既能達(dá)到目的,還能實(shí)現(xiàn)最大的并發(fā)性。所以,當(dāng)我們考慮事務(wù)T1中的SQL上加了什么鎖時(shí),就想一下,當(dāng)T1執(zhí)行時(shí),如果并發(fā)的事務(wù) T2不會(huì)觸及到T1的行,則T2無(wú)需被阻塞,如果T2的要 讀取/修改/插入 滿足T1條件的行時(shí),T2就得被T1阻塞。而T1阻塞T2的具體實(shí)現(xiàn)就是:T1在已存在的行上加index record lock使得T2無(wú)法觸碰已存在的行,以及,T1在不存在的行上加gap lock使得T2無(wú)法插入新的滿足條件的行。
前面我們說(shuō)過(guò)“加什么樣的鎖”與以下因素相關(guān)
1、當(dāng)前事務(wù)的隔離級(jí)別
2、SQL是一致性非鎖定讀(consistent nonlocking read)還是DML或鎖定讀(locking read)
3、SQL執(zhí)行時(shí)是否使用了索引,所使用索引的類型(主鍵索引,輔助索引、唯一索引)
我們來(lái)看一下,不同的隔離級(jí)別下,使用不同的索引時(shí),分別加什么鎖。在討論之前,我們先剔除無(wú)需討論的情況
首先,普通SELECT 使用一致性非鎖定讀,因此根本不存在鎖。無(wú)需討論;
再者,作為開(kāi)發(fā)者,我們幾乎從來(lái)不會(huì)使用到隔離級(jí)別RU和Serializable。這兩個(gè)隔離級(jí)別無(wú)需討論。
于是,剩下的就是 給定鎖定讀SELECT或DML(INSERT/UPDATE/DELETE)語(yǔ)句,在不同隔離級(jí)別下,使用不同類型的索引時(shí),分別會(huì)加什么樣的鎖?直接給出答案,其加鎖原則如下
一、RR時(shí),如果使用非唯一索引進(jìn)行搜索或掃描,則在所掃描的每一個(gè)索引記錄上都設(shè)置next-key lock。
這里“所掃描的每一個(gè)索引記錄”是指當(dāng)掃描執(zhí)行計(jì)劃中所使用的索引時(shí),搜索遇到的每一條記錄。WHERE條件是否排除掉某個(gè)數(shù)據(jù)行并沒(méi)有關(guān)系,InnoDB并不記得確切的WHERE條件,InnoDB倔強(qiáng)的只認(rèn)其掃描的索引范圍(index range) 。
你可能覺(jué)得InnoDB在設(shè)置鎖時(shí)蠻不講理,竟然不管WHERE條件排除掉的某些行,這不是大大增加了鎖的范圍了嘛。不過(guò),等我們了解了MySQL執(zhí)行SQL時(shí)的流程,這就好理解了。MySQL的執(zhí)行計(jì)劃只會(huì)選擇一個(gè)索引,使用一個(gè)索引來(lái)進(jìn)行掃描,MySQL執(zhí)行SQL語(yǔ)句的流程是,先由InnoDB引擎執(zhí)行索引掃描,然后,把結(jié)果返回給MySQL服務(wù)器,MySQL服務(wù)器會(huì)再對(duì)該索引條件之外的其他查詢條件進(jìn)行求值,從而得到最終結(jié)果集,而加鎖時(shí)只考慮InnoDB掃描的索引,由MySQL服務(wù)器求值的其他WHERE條件并不考慮。當(dāng)然,MySQL使用index_merge優(yōu)化時(shí)會(huì)同時(shí)使用多個(gè)索引的,不過(guò),這個(gè)時(shí)候設(shè)置鎖時(shí)也并不特殊,同樣,對(duì)于所用到的每一個(gè)索引,InnoDB在所掃描的每一個(gè)索引記錄上都設(shè)置next-key lock。
加的鎖一般是next-key lock,這種鎖住了索引記錄本身,還鎖住了每一條索引記錄前面的間隙,從而阻止其他事務(wù) 向 索引記錄前面緊接著的間隙中插入記錄。
如果在搜索中使用了輔助索引(secondary index),并且在輔助索引上設(shè)置了行鎖,則,InnoDB還會(huì)在 相應(yīng)的 聚集索引 上設(shè)置鎖;表未定義聚集索引時(shí),InnoDB自動(dòng)創(chuàng)建隱藏的聚集索引(索引名字是GEN_CLUST_INDEX),當(dāng)需要在聚集索引上設(shè)置鎖時(shí),就設(shè)置到此自動(dòng)創(chuàng)建的索引上。
二、RR時(shí),如果使用了唯一索引的唯一搜索條件,InnoDB只在滿足條件的索引記錄上設(shè)置index record lock,不鎖定索引記錄前面的間隙;如果用唯一索引作范圍搜索,依然會(huì)鎖定每一條被掃描的索引記錄前面的間隙,并且再在聚集索引上設(shè)置鎖。
三、RR時(shí),在第一個(gè)不滿足搜索條件的索引記錄上設(shè)置gap lock或next-key lock。
一般,等值條件時(shí)設(shè)置gap lock,范圍條件時(shí)設(shè)置next-key lock。此gap lock或next-key lock鎖住第一個(gè)不滿足搜索條件的記錄前面的間隙。
四、RR時(shí),INSERT在插入新行之前,必須首先為表上的每個(gè)索引設(shè)置insert intention lock。
每個(gè)insert intention lock的范圍都是(待插入行的某索引列的值, 此索引上從待插入行給定的值向下的第一個(gè)索引值)。只有當(dāng)insert intention lock與某個(gè)gap lock或next-key lock沖突時(shí),才能在performance_schema.data_locks看到insert intention lock。
五、RC時(shí),InnoDB只在完全滿足WHERE條件的行上設(shè)置index record lock。
六、RC時(shí),禁用了gap lock。
正因?yàn)榇?,RC時(shí)不存在gap lock或next-key lock。這是為什么呢?我們想一想啊,gap lock是用來(lái)解決phantom row問(wèn)題的,gap lock封鎖的區(qū)間內(nèi)不能插入新的行,因?yàn)椴迦霑r(shí)的insert intention lock會(huì)和gap lock沖突,從而阻止了新行的插入。但,隔離級(jí)別RC是允許phantom row的,因此RC時(shí)gap lock是被禁用的。
七、RR或RC時(shí),對(duì)于主鍵或唯一索引,當(dāng)有重復(fù)鍵錯(cuò)誤(duplicate-key error)時(shí),會(huì)在 重復(fù)的索引記錄上 設(shè)置 shared next-key lock或shared index record lock。這可能會(huì)導(dǎo)致死鎖。
假設(shè)T1, T2, T3三個(gè)事務(wù),T1已經(jīng)持有了X鎖,T2和T3發(fā)生了重復(fù)鍵錯(cuò)誤,因此T2和T3都在等待獲取S鎖,這個(gè)時(shí)候,當(dāng)T1回滾或提交釋放掉了X鎖,則T2和T3就都獲取到了S鎖,并且,T2和T3都請(qǐng)求X鎖,“T2和T3同時(shí)持有S鎖,且都在請(qǐng)求X鎖”,于是死鎖就產(chǎn)生了。
好了,規(guī)則都列出來(lái)了,是時(shí)候?qū)嵺`一把了。下面在展示鎖時(shí),我們同時(shí)指出了當(dāng)前所使用的隔離級(jí)別,表上的索引以及事務(wù)的SQL語(yǔ)句。
實(shí)踐一:搜索時(shí)無(wú)法使用索引,即全表掃描時(shí),InnoDB在表的全部行上都加鎖
上圖演示了:搜索條件無(wú)法使用索引時(shí),InnoDB不得不在表的全部行上都加鎖。所以,索引實(shí)在太重要了,查詢時(shí),它能加快查詢速度;更新時(shí),除了快速找到指定行,它還能減少被鎖定行的范圍,提高插入時(shí)的并發(fā)性。
實(shí)踐二:唯一索引和非唯一索引、等值查詢和范圍查詢加鎖的不同
搜索時(shí)使用 唯一索引 作等值查詢時(shí),InnoDB只需要加index record lock;搜索時(shí)使用 唯一索引作范圍查詢時(shí) 或 使用非唯一索引作任何查詢時(shí) ,InnoDB需要加next-key lock或gap lock。
示例1演示了:使用非唯一索引 idx_c 搜索或掃描時(shí),InnoDB要鎖住索引本身,還要鎖住索引記錄前面的間隙,即next-key lock: X 和 gap lock: X,GAP。next-key lock既鎖住索引記錄本身,還鎖住該索引記錄前面的間隙,gap lock只鎖住索引記錄前面的間隙。等值條件時(shí),在最后一個(gè)不滿足條件的索引記錄上設(shè)置gap lock。
示例2演示了:使用唯一索引 iux_b 的唯一搜索條件,即,使用唯一索引執(zhí)行等值查找時(shí),InnoDB只需鎖住索引本身,即index record lock: X, REC_NOT_GAP,并不鎖索引前面的間隙。
示例3演示了:使用唯一索引 iux_b 進(jìn)行范圍掃描時(shí),依然需要鎖定掃描過(guò)的每一個(gè)索引記錄,并且鎖住每一條索引記錄前面的間隙,即next-key lock: X。范圍條件時(shí),在最后一個(gè)不滿足條件的索引記錄上設(shè)置next-key lock。
實(shí)踐三:不同隔離級(jí)別加鎖的不同
無(wú)論何種隔離級(jí)別,SQL語(yǔ)句執(zhí)行時(shí),都是先由InnoDB執(zhí)行索引掃描,然后,返回結(jié)果集給MySQL服務(wù)器,MySQL服務(wù)器再對(duì)該索引條件之外的其他查詢條件進(jìn)行求值,從而得到最終結(jié)果集。
上圖中,在不同的隔離級(jí)別下,執(zhí)行了相同的SQL。無(wú)論何種隔離級(jí)別,PRIMARY上的index record lock總是會(huì)加的,我們不討論它。在idx_b上,隔離級(jí)別為RC時(shí),InnoDB加了index record lock,即:X,REC_NOT_GAP,隔離級(jí)別為RR時(shí),InnoDB加了next-key lock,即X。注意:RC時(shí)沒(méi)有g(shù)ap lock或next-key lock哦。
上圖演示了:事務(wù)的隔離級(jí)別也會(huì)影響到設(shè)置哪種鎖。如我們前面所說(shuō),gap lock是用來(lái)阻止phantom row的,而RC時(shí)是允許phantom row,所以,RC時(shí)禁用了gap lock。因此,上圖中,RC時(shí)沒(méi)有在索引上設(shè)置gap lock或next-key lock。
實(shí)踐四:操作不存在的索引記錄時(shí),也需要加鎖
上圖中,idx_b上并不存在b=266的索引記錄,那么,當(dāng)更新b=266的記錄時(shí),是否需要加鎖呢?是的,也需要加鎖
無(wú)論b=266是否存在,RR時(shí),InnoDB在第一個(gè)不滿足搜索條件的索引記錄上設(shè)置gap lock或next-key lock。一般,等值條件時(shí)設(shè)置gap lock,范圍條件時(shí)設(shè)置next-key lock。上圖中是等值條件,于是InnoDB設(shè)置gap lock,即上圖的X,GAP,其范圍是(226, 2222),正是此gap lock使得并發(fā)的事務(wù)無(wú)法插入b列大于等于266的值,RC時(shí),由于gap lock是被禁止的,因此,并不會(huì)加gap lock,并發(fā)的事務(wù)可以插入b列大于等于266的值。
上圖演示了:操作不存在的索引記錄時(shí),也需要加鎖。
實(shí)踐五:重復(fù)鍵錯(cuò)誤(duplicate-key error)時(shí),會(huì)加共享鎖。這可能會(huì)導(dǎo)致死鎖。
對(duì)于主鍵或唯一索引,當(dāng)有重復(fù)鍵錯(cuò)誤(duplicate-key error)時(shí),會(huì)在 重復(fù)的索引記錄上 設(shè)置 shared next-key lock或shared index record lock。這可能會(huì)導(dǎo)致死鎖。
上圖演示了:T1在主鍵1上設(shè)置exclusive index record lock。T2和T3插入時(shí),會(huì)產(chǎn)生重復(fù)鍵錯(cuò)誤,于是T2和T3都在主鍵1上設(shè)置了shared next-key lock。如上圖所示
如果此時(shí),T1 rollback釋放掉其所持有的index record lock,則T2和T3等待獲取的shared next-key lock都成功了,然后,T2和T3爭(zhēng)奪主鍵1上的index record lock,于是T2和T3就死鎖了,因?yàn)樗鼈z都持有shard next-key lock,雙方誰(shuí)都不會(huì)放棄已經(jīng)得到的shared next-key lock,于是,誰(shuí)都無(wú)法得到主鍵1的index record lock。
需要明確的是死鎖的可能性并不受隔離級(jí)別的影響,因?yàn)楦綦x級(jí)別改變的是讀操作的行為,而死鎖是由于寫操作產(chǎn)生的。死鎖并不可怕,MySQL會(huì)選擇一個(gè)犧牲者,然后,在系統(tǒng)變量innodb_lock_wait_timeout指定的秒數(shù)達(dá)到后,自動(dòng)回滾犧牲者事務(wù);從MySQL5.7開(kāi)始,新加入了系統(tǒng)變量innodb_deadlock_detect(默認(rèn)ON),如果開(kāi)啟此變量,則MySQL不會(huì)再等待,一旦探測(cè)到死鎖,就立即回滾犧牲者事務(wù)。
上圖演示了:在上圖的狀態(tài)下,當(dāng)T1 commit時(shí),T1釋放了主鍵1上的index record lock,于是T2和T3等待獲取的shared next-key lock都成功了,然后,T2和T3爭(zhēng)奪主鍵1上的index record lock,于是T2和T3死鎖了,因?yàn)樗鼈z都持有shard next-key lock,雙方誰(shuí)都不會(huì)放棄已經(jīng)得到的shared next-key lock,于是,誰(shuí)都無(wú)法得到主鍵1的index record lock。
五、performance_schema.data_locks中能看到全部的鎖嗎?
顯而易見(jiàn),performance_schema.data_locks并未顯示全部的鎖,那么,它顯示了哪些鎖呢?很不幸,我并未找到文檔說(shuō)這事,盡管文檔(https://dev.mysql.com/doc/refman/8.0/en/innodb-information-schema-transactions.html)說(shuō):“事務(wù)持有的每一個(gè)鎖 以及 事務(wù)被阻塞的每一個(gè)鎖請(qǐng)求,都在該表中占據(jù)一行”,但,我們很多例子都表明,它并未顯示全部的鎖。根據(jù)我的試驗(yàn),我猜測(cè)performance_schema.data_locks顯示的是WHERE條件所觸碰到的索引上的鎖,“WHERE條件所觸碰到的索引”是指SQL實(shí)際執(zhí)行時(shí)所使用的索引,也就是SQL執(zhí)行計(jì)劃的key列所顯示的索引,正因?yàn)榇?,INSERT時(shí)看不到任何鎖,update g set a=a+1 where b=22時(shí)只看到idx_b上的鎖。需要強(qiáng)調(diào)的是,這是我自己試驗(yàn)并猜測(cè)的,我并未在文檔中看到這種說(shuō)法。
假設(shè)T1和T2兩個(gè)事務(wù)操作同一個(gè)表,先執(zhí)行T1,此時(shí)盡管performance_schema.data_locks中只顯示T1的WHERE條件所觸碰到的索引上的鎖,但是,事實(shí)上在T1的WHERE條件觸碰不到的索引上,也是會(huì)設(shè)置鎖的。盡管表的索引idx并未被T1所觸碰到,即performance_schema.data_locks顯示T1在索引idx并沒(méi)有設(shè)置任何鎖,但,當(dāng)T2執(zhí)行 鎖定讀/插入/更新/刪除 時(shí)觸碰到了索引idx,T2才恍然發(fā)現(xiàn),原來(lái)T1已經(jīng)在索引idx上加鎖了。
我們來(lái)看下面的三個(gè)例子
“performance_schema.data_locks無(wú)法看到全部鎖”示例一
上圖演示了:T1執(zhí)行時(shí),只觸碰到了索引idx_b,T1執(zhí)行完后,在performance_schema.data_locks中只能看到idx_b上的鎖,看起來(lái)T1并未在idx_a上設(shè)置任何鎖;但,當(dāng)T2執(zhí)行觸碰到了索引idx_a時(shí),T2才恍然發(fā)現(xiàn),原來(lái)T1已經(jīng)在idx_a上設(shè)置了index record lock啦。
“performance_schema.data_locks無(wú)法看到全部鎖”示例二
插入新行時(shí),會(huì)先設(shè)置insert intention lock,插入成功后再在插入完成的行上設(shè)置index record lock。
上圖演示了:T1插入了新行,但,在performance_schema.data_locks中,我們既看不到T1設(shè)置的insert intention lock,也看不到T1設(shè)置的index record lock。這是因?yàn)門1的WHERE條件并未觸碰到任何索引(T1根本不存在WHERE條件),因此我們看不到T1的這兩個(gè)鎖;但,當(dāng)T2要?jiǎng)h除T1新插入的行時(shí),T2才恍然發(fā)現(xiàn),原來(lái)T1已經(jīng)在索引c2上設(shè)置了index record lock啦。
“performance_schema.data_locks無(wú)法看到全部鎖”示例三
插入新行時(shí),本來(lái)是不會(huì)在performance_schema.data_locks中顯示insert intention lock的,因?yàn)椴迦霑r(shí)WHERE條件并未觸碰到任何索引(插入時(shí)根本不存在WHERE條件)。
上圖演示了:T2插入新行時(shí)的insert intention lock 和 T1的gap lock沖突了,于是,我們得以在performance_schema.data_locks中觀察到T2插入新行時(shí)需要請(qǐng)求insert intentin lock。
-
存儲(chǔ)
+關(guān)注
關(guān)注
13文章
4226瀏覽量
85578 -
MySQL
+關(guān)注
關(guān)注
1文章
797瀏覽量
26399 -
Phantom
+關(guān)注
關(guān)注
0文章
4瀏覽量
1867
原文標(biāo)題:MySQL InnoDB 鎖介紹,及不同 SQL 語(yǔ)句分別加什么樣的鎖
文章出處:【微信號(hào):DBDevs,微信公眾號(hào):數(shù)據(jù)分析與開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論