MySQL 核心模塊揭秘—隱式鎖
1. 什么是隱式鎖?
前面我們介紹了行鎖的共享鎖、排他鎖。按照精確模式,它們又都可以細(xì)分為普通記錄鎖、間隙鎖、Next-Key 鎖。
另外,還有一種專門用于插入記錄場景的插入意向鎖。
事務(wù)讀寫記錄需要加這些行鎖時(shí),會發(fā)起加鎖操作,申請新的行鎖結(jié)構(gòu)或者復(fù)用已有的行鎖結(jié)構(gòu)。
有了對應(yīng)的行鎖結(jié)構(gòu),我們就可以通過 performance_schema.data_locks 表查詢到這些行鎖的加鎖情況了。InnoDB 內(nèi)部把這種有對應(yīng)鎖結(jié)構(gòu)的行鎖稱為顯式鎖。
隱式鎖,是相對于顯式鎖而言的,它也是一種行鎖,而且是普通記錄鎖的一種特殊存在形式。
顧名思義,既然是隱式鎖,也就意味著我們查詢不到它的加鎖情況。
我們之所以查詢不到,是因?yàn)殡[式鎖沒有對應(yīng)的行鎖結(jié)構(gòu),它就像空氣一樣,神在,形不在。
我們知道空氣是存在的,通常情況下,我們看不見,也摸不著。但是,熱空氣遇冷之后,凝結(jié)成小水珠,我們就能看得見,也能摸得著了。
我們也知道隱式鎖是存在的,卻查詢不到。它也會像空氣一樣,有被看見的時(shí)候嗎?
是的,它也有被看見的時(shí)候。但是,當(dāng)它被看見的時(shí)候,已經(jīng)換了一種形式,不再是隱式鎖了,而是變成了顯式鎖。
隱式鎖變成顯式鎖之后,我們就可以通過 performance_schema.data_locks 表查詢到加鎖情況了。
那么,問題來了,隱式鎖到底是被看見了,還是沒有被看見呢?
2. 怎么判斷存在隱式鎖?
隱式鎖,不僅可以存在于主鍵索引記錄上,還可以存在于二級索引記錄上。
在它變成顯式鎖之前,我們怎么判斷一條記錄上是否存在隱式鎖呢?
我根據(jù)代碼邏輯歸納了四種情況。
情況 1,事務(wù)執(zhí)行 insert 語句或者 update 語句插入一條記錄到主鍵索引中,事務(wù)提交之前,這條記錄上存在隱式鎖。
update 語句不是更新記錄嗎,怎么還會插入記錄?
如果你也有這樣的疑問,說明這是個(gè)好問題。
有一種場景:如果 update 語句更新了主鍵字段值,主鍵索引的原記錄會被標(biāo)記刪除,然后插入一條新記錄。
其中,原記錄的主鍵字段為更新之前的值,新記錄的主鍵字段為更新之后的值。
情況 2,事務(wù)執(zhí)行 insert 語句插入一條記錄到二級索引中,事務(wù)提交之前,這條記錄上存在隱式鎖。
情況 3,事務(wù)執(zhí)行 update 語句更新了二級索引的某個(gè)字段,二級索引的原記錄會被標(biāo)記刪除,然后插入一條新記錄,事務(wù)提交之前,原記錄和新記錄上都存在隱式鎖。
情況 4,事務(wù)執(zhí)行 delete 語句,如果掃描記錄時(shí)沒有使用二級索引,二級索引記錄不會被顯式加鎖。
二級索引記錄被標(biāo)記刪除之后,事務(wù)提交之前,記錄上都存在隱式鎖。
根據(jù)代碼邏輯歸納出所有情況是很困難的,為了幫助我們更好的判斷記錄上是否存在隱式鎖,我們有必要看看 InnoDB 代碼里的判斷邏輯長什么樣。
InnoDB 代碼里,判斷記錄上是否存在隱式鎖的邏輯,和索引類型有關(guān)。
對于主鍵索引,判斷邏輯比較簡單。
InnoDB 會從主鍵索引記錄的 DB_TRX_ID 字段中讀取事務(wù) ID,找到最后操作這條記錄的事務(wù)。
只要主鍵索引記錄上沒有顯式鎖,并且最后操作記錄的事務(wù)還沒有提交,就認(rèn)為這條記錄上存在隱式鎖。
對于二級索引,因?yàn)樗饕涗浿袥]有 DB_TRX_ID 字段,判斷邏輯會比主鍵索引復(fù)雜一點(diǎn)。
二級索引數(shù)據(jù)頁的頭信息中有個(gè) PAGE_MAX_TRX_ID 字段,表示最后修改數(shù)據(jù)頁中任意一條記錄的事務(wù) ID。
以某個(gè)二級索引中的一條記錄(S1)為例,判斷這條記錄上是否存在隱式鎖的主要步驟如下:
第 1 步,讀取 S1 所屬數(shù)據(jù)頁頭信息中的 PAGE_MAX_TRX_ID 字段,看看這個(gè)事務(wù) ID 對應(yīng)的事務(wù)是否已經(jīng)提交了。
如果事務(wù)已經(jīng)提交,說明 S1 上不存在隱式鎖。
如果事務(wù)還沒有提交,進(jìn)入第 2 步。
第 2 步,根據(jù) S1 中的主鍵字段,回表查詢對應(yīng)的主鍵索引記錄。
找到主鍵索引記錄之后,從它的 DB_TRX_ID 字段中讀取事務(wù) ID,看看這個(gè)事務(wù) ID 對應(yīng)的事務(wù)是否已經(jīng)提交了。
如果事務(wù)已經(jīng)提交,說明 S1 上不存在隱式鎖。
如果事務(wù)還沒有提交,那就麻煩了,需要進(jìn)一步判斷,這個(gè)代碼邏輯就很晦澀了。
不過,值得欣慰的是,雖然代碼邏輯很晦澀,但是用大白話描述起來可以很簡單。
用大白話描述是這樣的:只要這個(gè)還沒有提交的事務(wù)操作過 S1,不管這個(gè)操作是插入,還是刪除,都意味著 S1 上存在隱式鎖。
3. 轉(zhuǎn)換為顯式鎖
如果某條記錄上存在隱式鎖,在需要時(shí),會被轉(zhuǎn)換被顯式鎖。這個(gè)轉(zhuǎn)換主要發(fā)生在兩種場景下。
場景一,記錄(R1)上存在隱式鎖,其它事務(wù)(A)讀寫 R1 之前,如果需要對 R1 加行鎖,事務(wù) A 會把 R1 上的隱式鎖轉(zhuǎn)換為顯式鎖,然后等待 R1 上的行鎖被釋放之后,事務(wù) A 才能獲得鎖。
場景二,某個(gè)事務(wù)部分回滾時(shí),如果它操作過的記錄上存在隱式鎖,會被轉(zhuǎn)換為顯式鎖。
部分回滾,指的是把事務(wù)回滾到某個(gè)保存點(diǎn)。這個(gè)保存點(diǎn)可以是我們手動創(chuàng)建的保存點(diǎn),也可以是 InnoDB 內(nèi)部創(chuàng)建的保存點(diǎn)。
InnoDB 內(nèi)部創(chuàng)建的保存點(diǎn),主要用于插入記錄出現(xiàn)沖突時(shí),回滾已經(jīng)執(zhí)行的操作。
介紹完隱式鎖轉(zhuǎn)換為顯式鎖的場景,我們再來看看隱式鎖會被轉(zhuǎn)換成什么樣的顯式鎖。
前面我們介紹過,隱式鎖是普通記錄鎖的一種特殊存在形式,所以,它也是普通記錄鎖。
隱式鎖,既可以存在于剛剛插入的記錄上,也可以存在于標(biāo)記刪除的二級索引記錄上,所以,它又是一種排他鎖。
兩者綜合起來,隱式鎖本質(zhì)上相當(dāng)于排他普通記錄鎖。
發(fā)生轉(zhuǎn)換時(shí),隱式鎖會被轉(zhuǎn)換為排他普通記錄鎖。這個(gè)轉(zhuǎn)換邏輯是不是又簡單又粗暴?
4. 總結(jié)
隱式鎖,是排他普通記錄鎖的一種特殊存在形式。
我們查詢不到隱式鎖的加鎖情況,只能根據(jù)我們的經(jīng)驗(yàn)判斷記錄上是否存在隱式鎖。
在某些場景下,隱式鎖會被轉(zhuǎn)換為顯式鎖,然后,我們就可以通過 performance_schema.data_locks 表查詢到加鎖情況了。