MySQL 核心模塊揭秘 | 插入記錄,主鍵索引沖突加什么鎖?
1. 準(zhǔn)備工作
創(chuàng)建測(cè)試表:
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;
插入測(cè)試數(shù)據(jù):
INSERT INTO `t1` (`id`, `i1`) VALUES
(10, 101), (20, 201), (30, 301), (40, 401);
2. 加鎖情況
t1 表中已經(jīng)有一條 <id = 10> 的記錄,我們執(zhí)行以下 insert 語句,再插入一條 <id = 10> 的記錄。
begin;
insert into t1(id, i1) values (10, 1010);
因?yàn)樾虏迦胗涗浐捅碇性杏涗洿嬖谥麈I沖突,執(zhí)行 insert 語句之后,報(bào)錯(cuò)如下:
(1062, "Duplicate entry '10' for key 't1.PRIMARY'")
執(zhí)行以下 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 表示對(duì) <id = 10> 的記錄加了共享普通記錄鎖。
3. 原理分析
insert 語句執(zhí)行過程中,插入記錄到主鍵索引之前,需要先找插入記錄的目標(biāo)位置。
目標(biāo)位置為表中主鍵字段值小于等于新插入記錄中主鍵字段值的最后一條記錄之后。
以示例 SQL 為例,插入主鍵字段 <id = 10> 的記錄。
插入記錄到主鍵索引之前,先找到表中 id 小于等于 10 的最后一條記錄,也就是 <id = 10, i1 = 101>
這條記錄。新插入記錄的目標(biāo)位置就是這條記錄之后。
InnoDB 發(fā)現(xiàn)表中已經(jīng)有一條 <id = 10> 的記錄,現(xiàn)在又要插入一條 <id = 10> 的記錄,可是主鍵索引中不允許存在重復(fù)記錄,這可怎么辦才好?
直接報(bào)錯(cuò)嗎?
那樣簡單粗暴就過于武斷了。
InnoDB 還需要對(duì)表中 <id = 10> 的記錄驗(yàn)明正身,確定它是一條有效的記錄。
如果表中 <id = 10> 的記錄已經(jīng)被其它已提交事務(wù)標(biāo)記刪除,只是還沒有被清理,它就不是有效的記錄了。這種情況下,新記錄可以正常插入,不會(huì)報(bào)錯(cuò)。
否則,新記錄和表中已有記錄沖突,不能插入,就可以報(bào)錯(cuò)了。
為了防止其它事務(wù)更新或者刪除這條記錄,檢查表中記錄是否有效之前,InnoDB 會(huì)對(duì)這條記錄加共享普通記錄鎖。
這就是示例 SQL 執(zhí)行過程中對(duì) <id = 10> 的記錄加共享普通記錄鎖的原因。
如果表中 <id = 10> 的記錄已經(jīng)被標(biāo)記刪除,但是刪除這條記錄的事務(wù)還沒有提交怎么辦?
那我們看到的加鎖情況就不一樣了。
我們可以模擬下這個(gè)場景,創(chuàng)建 2 個(gè) MySQL 連接,分別執(zhí)行 delete 語句和 insert 語句。
-- 連接 1(事務(wù) 1)
begin;
delete from t1 where id = 10;
-- 連接 2(事務(wù) 2)
begin;
insert into t1(id, i1) values (10, 1010);
然后執(zhí)行以下 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
事務(wù) 247911 執(zhí)行刪除操作對(duì) <id = 10> 的記錄加了排他普通記錄鎖。
事務(wù) 247916 想要對(duì) <id = 10> 的記錄加共享普通記錄鎖被阻塞,進(jìn)入等待狀態(tài)。
4. 總結(jié)
沒有需要總結(jié)的內(nèi)容了。
但是有兩個(gè)問題:事務(wù) 247911 提交或者回滾之后,加鎖情況是什么樣的?為什么會(huì)這樣?