详解:InnoDB中各SQL语句的加锁类型——FOREIGN KEY

119 阅读5分钟

结构

数据

父表

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;

结论

  1. 隔离级别:RR

    1. 删除父表中的记录

      1. 如果被删除的记录已被子表引用,则给子表受影响的索引,加S记录锁

      2. 否则,给子表受影响的索引,加S-GAP锁

    2. 删除子表中的记录,父表不加锁

    3. 插入父表记录,子表不加锁

    4. 插入子表记录

      1. 如果子表记录中引用的外键值在父表中存在,则给父表,加S记录锁

      2. 否则,给父表加S-GAP锁

  2. 隔离级别:RC

    1. 删除父表中的记录

      1. 如果被删除的记录已被子表引用,则给子表受影响的索引,加S记录锁

      2. 否则,子表不加锁

    2. 删除子表中的记录,父表不加锁

    3. 插入父表记录,子表不加锁

    4. 插入子表记录

      1. 如果子表记录中引用的外键值在父表中存在,则给父表,加S记录锁

      2. 否则,子表不加锁

  3. 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锁

  4. 1401行,隔离级别<=RC时,跳过GAP锁

  1. 1654行的else,表示外键值未在父表中找到,即外键检测失败

  2. 1659行,在RC隔离接下,为false,即RC隔离级别不加GAP锁