詳解 MySql InnoDB 中的三種行鎖(記錄鎖、間隙鎖與臨鍵鎖)

wwj-jww發表於2020-10-14

前言
InnoDB 通過 MVCC 和 NEXT-KEY Locks,解決了在可重複讀的事務隔離級別下出現幻讀的問題。MVCC 我先挖個坑,日後再細講,這篇文章我們主要來談談那些可愛的鎖。

什麼是幻讀?
幻讀是在可重複讀的事務隔離級別下會出現的一種問題,簡單來說,可重複讀保證了當前事務不會讀取到其他事務已提交的 UPDATE 操作。但同時,也會導致當前事務無法感知到來自其他事務中的 INSERT 或 DELETE 操作,這就是幻讀。

關於行鎖我們要知道的
行鎖在 InnoDB 中是基於索引實現的,所以一旦某個加鎖操作沒有使用索引,那麼該鎖就會退化為表鎖。

可愛的鎖
記錄鎖(Record Locks)
顧名思義,記錄鎖就是為某行記錄加鎖,它封鎖該行的索引記錄:

– id 列為主鍵列或唯一索引列
SELECT * FROM table WHERE id = 1 FOR UPDATE;
複製程式碼
id 為 1 的記錄行會被鎖住。

需要注意的是:id 列必須為唯一索引列或主鍵列,否則上述語句加的鎖就會變成臨鍵鎖。

同時查詢語句必須為精準匹配(=),不能為 >、<、like等,否則也會退化成臨鍵鎖(感謝評論區 @decodes 提醒)。

其他實現
在通過 主鍵索引 與 唯一索引 對資料行進行 UPDATE 操作時,也會對該行資料加記錄鎖:

– id 列為主鍵列或唯一索引列
UPDATE SET age = 50 WHERE id = 1;
複製程式碼
間隙鎖(Gap Locks)
間隙鎖基於非唯一索引,它鎖定一段範圍內的索引記錄。間隙鎖基於下面將會提到的Next-Key Locking 演算法,請務必牢記:使用間隙鎖鎖住的是一個區間,而不僅僅是這個區間中的每一條資料。

SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
複製程式碼
即所有在(1,10)區間內的記錄行都會被鎖住,所有id 為 2、3、4、5、6、7、8、9 的資料行的插入會被阻塞,但是 1 和 10 兩條記錄行並不會被鎖住。

除了手動加鎖外,在執行完某些 SQL 後,InnoDB 也會自動加間隙鎖,這個我們在下面會提到。

臨鍵鎖(Next-Key Locks)
Next-Key 可以理解為一種特殊的間隙鎖,也可以理解為一種特殊的演算法。通過臨建鎖可以解決幻讀的問題。 每個資料行上的非唯一索引列上都會存在一把臨鍵鎖,當某個事務持有該資料行的臨鍵鎖時,會鎖住一段左開右閉區間的資料。需要強調的一點是,InnoDB 中行級鎖是基於索引實現的,臨鍵鎖只與非唯一索引列有關,在唯一索引列(包括主鍵列)上不存在臨鍵鎖。

假設有如下表:
MySql,InnoDB,Repeatable-Read:table(id PK, age KEY, name)

id age name
1 10 Lee
3 24 Soraka
5 32 Zed
7 45 Talon
該表中 age 列潛在的臨鍵鎖有:
(-∞, 10],
(10, 24],
(24, 32],
(32, 45],
(45, +∞],

在事務 A 中執行如下命令:

– 根據非唯一索引列 UPDATE 某條記錄
UPDATE table SET name = Vladimir WHERE age = 24;
– 或根據非唯一索引列 鎖住某條記錄
SELECT * FROM table WHERE age = 24 FOR UPDATE;
複製程式碼
不管執行了上述 SQL 中的哪一句,之後如果在事務 B 中執行以下命令,則該命令會被阻塞:

INSERT INTO table VALUES(100, 26, ‘Ezreal’);
複製程式碼
很明顯,事務 A 在對 age 為 24 的列進行 UPDATE 操作的同時,也獲取了 (24, 32] 這個區間內的臨鍵鎖。

不僅如此,在執行以下 SQL 時,也會陷入阻塞等待:

INSERT INTO table VALUES(100, 30, ‘Ezreal’);
複製程式碼
那最終我們就可以得知,在根據非唯一索引 對記錄行進行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE 操作時,InnoDB 會獲取該記錄行的 臨鍵鎖 ,並同時獲取該記錄行下一個區間的間隙鎖。

即事務 A在執行了上述的 SQL 後,最終被鎖住的記錄區間為 (10, 32)。

總結
InnoDB 中的行鎖的實現依賴於索引,一旦某個加鎖操作沒有使用到索引,那麼該鎖就會退化為表鎖。
記錄鎖存在於包括主鍵索引在內的唯一索引中,鎖定單條索引記錄。
間隙鎖存在於非唯一索引中,鎖定開區間範圍內的一段間隔,它是基於臨鍵鎖實現的。
臨鍵鎖存在於非唯一索引中,該型別的每條記錄的索引上都存在這種鎖,它是一種特殊的間隙鎖,鎖定一段左開右閉的索引區間。

相關文章