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