Post

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 锁阻塞。

innodb-insert-intention-locks

隐式锁

新插入的记录,不生成锁结构,但由于事务 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加锁情况
T0begin;begin; 
T1insert 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锁
T3insert 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.