MySQL 核心模塊揭秘 | 插入記錄,主鍵索引沖突加什么鎖?
1. 準備工作
創建測試表:
CREATE TABLE `t1` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`i1` int DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_i1` (`i1`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
插入測試數據:
INSERT INTO `t1` (`id`, `i1`) VALUES
(10, 101), (20, 201), (30, 301), (40, 401);
2. 加鎖情況
t1 表中已經有一條 <id = 10> 的記錄,我們執行以下 insert 語句,再插入一條 <id = 10> 的記錄。
begin;
insert into t1(id, i1) values (10, 1010);
因為新插入記錄和表中原有記錄存在主鍵沖突,執行 insert 語句之后,報錯如下:
(1062, "Duplicate entry '10' for key 't1.PRIMARY'")
執行以下 select 語句查詢加鎖情況:
select
engine_transaction_id, object_name,
lock_type, lock_mode, lock_status, lock_data
from performance_schema.data_locks
where object_name = 't1'
and lock_type = 'RECORD'\G
***************************[ 1. row ]***************************
engine_transaction_id | 247910
object_name | t1
lock_type | RECORD
lock_mode | S,REC_NOT_GAP
lock_status | GRANTED
lock_data | 10
lock_data = 10, lock_mode = S,REC_NOT_GAP 表示對 <id = 10> 的記錄加了共享普通記錄鎖。
3. 原理分析
insert 語句執行過程中,插入記錄到主鍵索引之前,需要先找插入記錄的目標位置。
目標位置為表中主鍵字段值小于等于新插入記錄中主鍵字段值的最后一條記錄之后。
以示例 SQL 為例,插入主鍵字段 <id = 10> 的記錄。
插入記錄到主鍵索引之前,先找到表中 id 小于等于 10 的最后一條記錄,也就是 <id = 10, i1 = 101>
這條記錄。新插入記錄的目標位置就是這條記錄之后。
InnoDB 發現表中已經有一條 <id = 10> 的記錄,現在又要插入一條 <id = 10> 的記錄,可是主鍵索引中不允許存在重復記錄,這可怎么辦才好?
直接報錯嗎?
那樣簡單粗暴就過于武斷了。
InnoDB 還需要對表中 <id = 10> 的記錄驗明正身,確定它是一條有效的記錄。
如果表中 <id = 10> 的記錄已經被其它已提交事務標記刪除,只是還沒有被清理,它就不是有效的記錄了。這種情況下,新記錄可以正常插入,不會報錯。
否則,新記錄和表中已有記錄沖突,不能插入,就可以報錯了。
為了防止其它事務更新或者刪除這條記錄,檢查表中記錄是否有效之前,InnoDB 會對這條記錄加共享普通記錄鎖。
這就是示例 SQL 執行過程中對 <id = 10> 的記錄加共享普通記錄鎖的原因。
如果表中 <id = 10> 的記錄已經被標記刪除,但是刪除這條記錄的事務還沒有提交怎么辦?
那我們看到的加鎖情況就不一樣了。
我們可以模擬下這個場景,創建 2 個 MySQL 連接,分別執行 delete 語句和 insert 語句。
-- 連接 1(事務 1)
begin;
delete from t1 where id = 10;
-- 連接 2(事務 2)
begin;
insert into t1(id, i1) values (10, 1010);
然后執行以下 select 語句查看加鎖情況:
select
engine_transaction_id, object_name,
lock_type, lock_mode, lock_status, lock_data
from performance_schema.data_locks
where object_name = 't1'
and lock_type = 'RECORD'\G
***************************[ 1. row ]***************************
engine_transaction_id | 247916
object_name | t1
lock_type | RECORD
lock_mode | S,REC_NOT_GAP
lock_status | WAITING
lock_data | 10
***************************[ 2. row ]***************************
engine_transaction_id | 247911
object_name | t1
lock_type | RECORD
lock_mode | X,REC_NOT_GAP
lock_status | GRANTED
lock_data | 10
事務 247911 執行刪除操作對 <id = 10> 的記錄加了排他普通記錄鎖。
事務 247916 想要對 <id = 10> 的記錄加共享普通記錄鎖被阻塞,進入等待狀態。
4. 總結
沒有需要總結的內容了。
但是有兩個問題:事務 247911 提交或者回滾之后,加鎖情況是什么樣的?為什么會這樣?