MySQL并发写入唯一键死锁
 MySQL并发写入唯一键死锁 
 行锁类型
记录锁(RECORD LOCK)
对索引记录加锁。
间隙锁(GAP LOCK,也叫范围锁)
对索引记录的所在间隙加锁,在 RR 隔离级别下,用于解决幻读的问题(实际上在 RC 隔离级别下,在重复键检查和外键检查时)。
S 间隙锁和 X 间隙锁是兼容的,不同的事务可以在同一个间隙加锁。
NEXT-KEY 锁
相当于 RECORD LOCK + GAP LOCK。
插入意向锁(innodb-insert-intention-locks)
GAP 锁的一种,在执行 INSERT 前,如果待插入记录的下一条记录上被加了 GAP 锁,则 INSERT 语句被阻塞,且生成一个插入意向锁。
仅会被 GAP 锁阻塞。
隐式锁
新插入的记录,不生成锁结构,但由于事务 ID 的存在,相当于加了隐式锁;别的事务要对这条记录加锁前,先帮助其生成一个锁结构,然后再进入等待状态。
测试
MySQL 8.0.32 transaction_isolation:READ-COMMITTED
1
2
3
4
5
# 查看当前事务加的锁
select * from performance_schema.data_locks;
# 查看死锁日志
show engine innodb status;
表结构:
1
2
3
4
5
6
CREATE TABLE `logistic_base_info` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `logistic_code` varchar(30) NOT NULL COMMENT '物流单号',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uni_logistic_code` (`logistic_code`)
) ENGINE=InnoDB CHARSET=utf8mb4 COMMENT='物流单基础信息表';
| 时刻 | 事务1 | 事务2 | 加锁情况 | 
|---|---|---|---|
| T0 | begin; | begin; | |
| T1 | insert into logistic_base_info (logistic_code) values (7); | 事务1获取IX锁 | |
| T2 | insert into logistic_base_info (logistic_code) values (7); | 事务1获取IX+X锁;事务2获取IX锁,等待S锁 | |
| T3 | insert into logistic_base_info (logistic_code) values (6); | 死锁回滚事务2,事务1获取IX+X+GAP+INSERT INTENTION锁 | 
T1 时刻
事务1 插入记录成功,此时对应的索引记录被隐式锁保护,未生成锁结构。
T2 时刻
事务2 插入记录检测到插入值和 事务1 唯一键冲突。 事务2 帮助 事务1 对 logistic_code=7 的记录产生了一个显式的锁结构。 事务2 自身产生 S 型的 NEXT-KEY LOCK,请求范围为 (-∞,7],但是其只能获取到 (-∞,7) 的 GAP LOCK,而被 事务1 的 logistic_code=7 的X记录锁阻塞。
T3 时刻
事务1 插入 logistic_code=6,被 事务2 (-∞,7) 的 GAP LOCK 阻塞。
参考:https://segmentfault.com/a/1190000044183184
 This post is licensed under  CC BY 4.0  by the author.