1. 什么是幻讀?
幻讀是在可重復讀的事務隔離級別下會出現的一種問題,簡單來說,可重復讀保證了當前事務不會讀取到其他事務已提交的 UPDATE 操作。但同時,也會導致當前事務無法感知到來自其他事務中的 INSERT 或 DELETE 操作,這就是幻讀。
關於行鎖我們要知道的
行鎖在 InnoDB 中是基於索引實現的,所以一旦某個加鎖操作沒有使用索引,那么該鎖就會退化為表鎖。
2. 鎖的分類
1)記錄鎖(Record Locks)
顧名思義,記錄鎖就是為某行記錄加鎖,它封鎖該行的索引記錄:
-- id 列為主鍵列或唯一索引列
SELECT * FROM table WHERE id = 1 FOR UPDATE;
id 為 1 的記錄行會被鎖住。
需要注意的是:id 列必須為唯一索引列或主鍵列,否則上述語句加的鎖就會變成臨鍵鎖。
同時查詢語句必須為精准匹配(=),不能為 >、<、like等,否則也會退化成臨鍵鎖。
其他實現
在通過 主鍵索引 與 唯一索引 對數據行進行 UPDATE 操作時,也會對該行數據加記錄鎖:
-- id 列為主鍵列或唯一索引列
UPDATE SET age = 50 WHERE id = 1;
2)間隙鎖(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 也會自動加間隙鎖,這個我們在下面會提到。
3)臨鍵鎖(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)。
4. 總結
①:InnoDB 中的行鎖的實現依賴於索引,一旦某個加鎖操作沒有使用到索引,那么該鎖就會退化為表鎖。
②:記錄鎖存在於包括主鍵索引在內的唯一索引中,鎖定單條索引記錄。
③:間隙鎖存在於非唯一索引中,鎖定開區間范圍內的一段間隔,它是基於臨鍵鎖實現的。
④:臨鍵鎖存在於非唯一索引中,該類型的每條記錄的索引上都存在這種鎖,它是一種特殊的間隙鎖,鎖定一段左開右閉的索引區間。