type
status
date
slug
summary
tags
category
icon
password
前言
曾經在正式環境遇過一個特殊的情境,我們都知道
DEADLOCK
的成因在於不同的加鎖順序,加上這兩句 WHERE
條件是相似的語法,卻還是遇到了 DEADLOCK
,因此我的第一個反應是兩個 TRANSACTION
中已執行語句的 LOCK
未釋放,導致互相阻塞對方引起的。不過事後查看 LOG
加上詢問開發確認了該語句皆是單獨執行,只好回頭透過 EXPLAIN
確認執行計畫,發現兩句語法走了不同的 INDEX
以此找到思路,最後了解發生成因並找到解決方案,更重要的是知道了 隱式鎖(implicit lock)
的存在。重現


單獨測試
- 查看兩句語法單獨執行的 LOCK 狀況


- 先執行 Transaction 1 再執行 Transaction 2

- 先執行 Transaction 2 再執行 Transaction 1,可以發現 Transaction 2 多了一個 LOCK

經由以上測試,我們確認了此例的 Lock 的成因如下表格-

在分析個過程中,我們可以看到當優先執行
Transaction 2
時,我們可以看到最後一個 wating Lock ID_report (no gap)
是不存在的,直到我們執行 Transaction 1
時,這一個 LOCK
才會出現,這就是 隱式鎖(implicit lock)
。什麼是隱式鎖?
在 MySQL官方文檔 中關於隱式鎖就只有這麼一條說明-
When UPDATE modifies a clustered index record, implicit locks are taken on affected secondary index records.
資訊量實在不是很足夠,再加上討論到的文章其實不是很多,因此只好搭配少量的文章搭配源碼來了解,下方出現的源碼取自 8.0 版本,並加上一些個人加上的註解方便理解。
延遲加鎖機制
讓我們試想一下,如果一張表上面有多個索引,這樣在異動其中一筆資料的時候,豈不是要在好幾個 B+Tree 上同時加鎖嗎 ?
加鎖也是一筆開銷,如果衝突的可能性很小的時候,多數的鎖應該都是不必要的。
Innodb 實現了一個延遲加鎖機制,以此來減少加鎖的數量,減少效能損耗的同時也提升併發性能。
隱式鎖的簡介
Innodb 實現的延遲加鎖的機制,在源碼中被稱為
隱式鎖(implicit lock)
隱式鎖沒有實際加鎖,而是一種
logical entity
,transaction 是否持有是透過一些過程來計算的。 隱式鎖中有一個重要的元素:
db_trx_id
!用來儲存產生該筆紀錄的 transaction ID
,這是在 MVCC
中實現 快照讀
也有使用到的元素。隱式鎖的特點
- 只有在很可能發生衝突時才加鎖,減少鎖的數量
- 隱式鎖式針對 B+Tree 上被修改的紀錄,因此都是
record lock
,不可能是gap lock
或next-key lock
- 和顯式鎖的區別:
隱式鎖的使用
INSERT
操作一般只會加隱式鎖,不加顯示鎖。除了以下情況:- 若
INSERT
的位置有gap lock
時,則會加上insert intention lock
。 - 若
INSERT
的位置發生唯一鍵衝突時,則會將對方的隱式鎖升級為顯示鎖,自己則加上shared lock
等待。




UPDATE
和DELETE
操作在查詢時,會直接對查詢走的index
和primary key
使用顯示鎖,其他的index
使用隱式鎖。
隱式鎖的具體過程
- 如果 transaction 要獲取行鎖 (不論是顯式或隱式),則需要先判斷是否存在活躍的隱式鎖
- cluster index:首先取得在該紀錄上持有隱式鎖的 transaction id,隨後透過 cluster index 中的隱藏欄位
db_trx_id
判斷前者是否為活躍事務。 - secondary index:
- 從 secondary index page 中取得
PAGE_MAX_TRX_ID (T1)
- 取得 InnoDB 活躍中事務中
最小的 Transaction ID (T2)
- 假如 T1 < T2 則說明修改這個 page 的 T1 已經 commit ,因此不存在隱式鎖。

PAGE_MAX_TRX_ID
:該字段存在於 secondary index page 中,用於保存修改該 page 的最大 Transaction ID,當該 page 的任何紀錄被更新後,都會更新此值。
反之,則必須再透過 cluster index 搭配 undo log 回溯舊版本數據進行判斷,此步驟較為複雜暫不詳解 (可透過
storage\innobase\row\row0vers.cc
中 row_vers_impl_x_locked
和 row_vers_impl_x_locked_low
了解詳細過程)

- 若為活躍事務,則為該活躍事務將
implicit lock
轉換為explicit lock
另外可已注意到
implicit lock
轉換後的 explicit lock
不會有 gap lock

- 等待加鎖成功後修改數據,並且將自己的 Transaction id 寫入
db_trx_id
;或者是 timeout。