表
结构
数据
父表
CREATE TABLE `parent` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT
CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
子表
CREATE TABLE `child` (
`id` int NOT NULL AUTO_INCREMENT,
`pid` int DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_pid` (`pid`),
CONSTRAINT `child_ibfk_1`
FOREIGN KEY (`pid`) REFERENCES `parent` (`id`)
) ENGINE=InnoDB DEFAULT
CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
试验
设置隔离级别:RR或RC,按情况设置
-- RR
-- set session transaction isolation level repeatable read;
-- RC
set session transaction isolation level read committed;
select @@transaction_isolation;
试验1:隔离级别:RR,删除父表中的记录,子表加S记录锁或S-GAP锁
T1
备注
begin;
delete from parent where id = 20;
-- 此时的performance_schema.data_locks表
如果父表中的该记录,未被子表引用,那么子表被加S-GAP锁
delete from parent where id = 30;
-- 报错,外键约束检查失败,此时的performance_schema.data_locks表
如果父表中的该记录,已被子表引用,那么子表被加S记录锁
rollback;
试验2:隔离级别:RR,删除子表中的记录,父表不加锁
T1
备注
begin;
delete from child where pid = 30;
-- 此时的performance_schema.data_locks表
父表没有加锁
rollback;
试验3:隔离级别:RR,插入子表,父表加S记录锁或S-GAP锁,子表加X记录锁或X-GAP锁
T1
T2
备注
begin;
begin;
insert into child(pid,name) values (20, 'xxx');
select * from child where pid = 20 for share;
-- 会卡住,此时的performance_schema.data_locks表
父表有对应外键记录时
-
父表加S记录锁
-
子表加X记录锁。这里的select ... For share 是为了把X记录锁显现出来
insert into child(id, pid,name) values (35, 35, 'xxx');
-- 报错,外键约束失败,此时的performance_schema.data_locks表
父表无对应外键记录时
-
父表加S-GAP锁
-
子表加X-GAP锁
rollback;
rollback;
试验4:隔离级别:RR,插入父表,父表加X记录锁,子表不加锁
T1
T2
备注
begin;
begin;
insert into parent(id, name) values (25, 'xxx');
select * from parent where id = 25 for share;
-- 会卡住,此时的performance_schema.data_locks表
父表加X记录锁
这里的select ... For share 是为了把X记录锁显现出来
rollback;
rollback;
试验5:隔离级别:RC,删除父表中的记录,子表加S记录锁或不加锁
T1
备注
begin;
delete from parent where id = 20;
-- 此时的performance_schema.data_locks表
如果父表中的该记录,未被子表引用,那么子表不加锁
delete from parent where id = 30;
-- 报错,外键约束检查失败,此时的performance_schema.data_locks表
如果父表中的该记录,已被子表引用,那么子表被加S记录锁
rollback;
试验6:隔离级别:RC,删除子表中的记录,父表不加锁
T1
备注
begin;
delete from child where pid = 30;
-- 此时的performance_schema.data_locks表
父表没有加锁
rollback;
试验7:隔离级别:RC,插入子表,父表加S记录锁或不加锁,子表加X记录锁或不加锁
T1
T2
备注
begin;
begin;
insert into child(pid,name) values (20, 'xxx');
select * from child where pid = 20 for share;
-- 会卡住,此时的performance_schema.data_locks表
父表有对应外键记录时
-
父表加S记录锁
-
子表加X记录锁。这里的select ... For share 是为了把X记录锁显现出来
insert into child(id, pid,name) values (35, 35, 'xxx');
-- 报错,外键约束失败,此时的performance_schema.data_locks表
父表无对应外键记录时
-
父表不加锁
-
子表不加锁
rollback;
rollback;
试验8:隔离级别:RC,插入父表,父表加X记录锁,子表不加锁
T1
T2
备注
begin;
begin;
insert into parent(id, name) values (25, 'xxx');
select * from parent where id = 25 for share;
-- 会卡住,此时的performance_schema.data_locks表
父表加X记录锁
这里的select ... For share 是为了把X记录锁显现出来
rollback;
rollback;
结论
-
隔离级别:RR
-
删除父表中的记录
-
如果被删除的记录已被子表引用,则给子表受影响的索引,加S记录锁
-
否则,给子表受影响的索引,加S-GAP锁
-
-
删除子表中的记录,父表不加锁
-
插入父表记录,子表不加锁
-
插入子表记录
-
如果子表记录中引用的外键值在父表中存在,则给父表,加S记录锁
-
否则,给父表加S-GAP锁
-
-
-
隔离级别:RC
-
删除父表中的记录
-
如果被删除的记录已被子表引用,则给子表受影响的索引,加S记录锁
-
否则,子表不加锁
-
-
删除子表中的记录,父表不加锁
-
插入父表记录,子表不加锁
-
插入子表记录
-
如果子表记录中引用的外键值在父表中存在,则给父表,加S记录锁
-
否则,子表不加锁
-
-
-
MySQL参考手册上所说的
Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to[READ COMMITTED](https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html#isolevel_read-committed)``. In this case, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.“RC隔离级别下,GAP锁仅用于外键约束检测和重复键检测”,实际未能在RC隔离级别下复现外键约束检测的GAP锁,结合row_ins_check_foreign_constraint源码(MySQL 8.0),猜测是参考手册描述错误,即RC隔离级别下的外键约束检测,不使用GAP锁 -
1401行,隔离级别<=RC时,跳过GAP锁
-
1654行的else,表示外键值未在父表中找到,即外键检测失败
-
1659行,在RC隔离接下,为false,即RC隔离级别不加GAP锁