InnoDB死锁技术问询:两事务同页持有X锁的疑问
死锁问题解析
环境与背景
- MySQL版本:v8.0.23
- 死锁日志(执行
show engine innodb status输出):
2023-04-13 09:25:19 0x7f65e5d5c700 *** (1) 事务: TRANSACTION 667552221,ACTIVE 0秒 正在插入 使用的mysql表1个,已锁定1个 LOCK WAIT 3个锁结构,堆大小1136,2个行锁,undo日志条目1 MySQL线程ID 3662804,OS线程句柄140095257151232,查询ID 3727267470 x.x.x.x x.x.x.x admin update Insert into acl (`id`) values ("456085798018673829") *** (1) 持有锁: RECORD LOCKS space id 14 page no 198202 n bits 80 index bGhRuWwvrN of table `testdb`.`acl` trx id 667552221 lock_mode X locks gap before rec Record lock, heap no 10 PHYSICAL RECORD: n_fields 7; compact format; info bits 0 *** (1) 等待授予的锁: RECORD LOCKS space id 14 page no 198202 n bits 80 index bGhRuWwvrN of table `testdb`.`acl` trx id 667552221 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 10 PHYSICAL RECORD: n_fields 7; compact format; info bits 0 *** (2) 事务: TRANSACTION 667552222,ACTIVE 0秒 正在插入 使用的mysql表1个,已锁定1个 LOCK WAIT 3个锁结构,堆大小1136,2个行锁,undo日志条目1 MySQL线程ID 3662785,OS线程句柄140056638150400,查询ID 3727267471 x.x.x.x x.x.x.x admin update Insert into acl (`id`) values ("456085798018677418") *** (2) 持有锁: RECORD LOCKS space id 14 page no 198202 n bits 80 index bGhRuWwvrN of table `testdb`.`acl` trx id 667552222 lock_mode X locks gap before rec Record lock, heap no 10 PHYSICAL RECORD: n_fields 7; compact format; info bits 0 *** (2) 等待授予的锁: RECORD LOCKS space id 14 page no 198202 n bits 80 index bGhRuWwvrN of table `testdb`.`acl` trx id 667552222 lock_mode X locks gap before rec insert intention waiting Record lock, heap no 10 PHYSICAL RECORD: n_fields 7; compact format; info bits 0
- 表结构:
CREATE TABLE `ACL` ( `id` varchar(255) NOT NULL, `tenant_id` varchar(255) NOT NULL, `cname` varchar(50) NOT NULL, `rce_id` varchar(50) NOT NULL, `rce_type` int NOT NULL, `aen_id` varchar(50) NOT NULL, `prs` varchar(50) NOT NULL, `status` enum('ACTIVE','INACTIVE') NOT NULL, `elt_deny` tinyint(1) DEFAULT NULL, PRIMARY KEY (`id`,`tenant_id`), UNIQUE KEY `bGhRuWwvrN` (`rce_id`,`rce_type`,`aen_id`,`prs`,`cname`,`tenant_id`), KEY `tenant_id$cname$prs$aen_id` (`tenant_id`,`cname`,`prs`,`aen_id`), KEY `FZhIAdwsXu` (`tenant_id`,`cname`,`rce_type`,`prs`,`aen_id`), KEY `FZhIAdwsXu123` (`tenant_id`,`cname`,`rce_type`,`aen_id`) )
问题解答
1. 两个事务为何能同时持有同一页面的X锁?
它们持有的不是页面锁,而是间隙锁(Gap Lock)——日志里的lock_mode X locks gap before rec已经明确标注。
InnoDB的间隙锁是用来防止幻读的,它锁定的是索引记录之间的空隙,而非整个页面。间隙锁有个关键特性:多个事务可以同时对同一个间隙加排他间隙锁——因为间隙锁的核心目的是阻止其他事务往这个间隙里插数据,而非阻止其他事务加同样的间隙锁。
日志里的space id和page no相同,只是说明这两个间隙锁都落在同一个索引页面上,但它们锁定的是同一个间隙,InnoDB允许这种重复的间隙锁共存,互不冲突。
2. 为什么两个事务等待同一锁会引发死锁?
这两个事务都在等待插入意向锁(Insert Intention Lock),这是插入操作专属的特殊间隙锁,需要先检查目标间隙是否被其他事务的间隙锁占用。
死锁的形成逻辑:
- 事务1先给目标间隙加了排他间隙锁,随后尝试插入数据,需要获取插入意向锁;
- 同时事务2也给同一个间隙加了排他间隙锁,同样尝试插入数据,需要获取插入意向锁;
- 插入意向锁必须等待其他事务的间隙锁释放,但两个事务都持有对方需要的间隙锁,且都不会主动释放(因为它们都在等插入意向锁),形成了循环等待:事务1等事务2释放间隙锁,事务2等事务1释放间隙锁,最终触发InnoDB的死锁检测机制,终止其中一个事务。
本质上是因为两个插入操作在同一唯一索引的相同间隙上同时加了间隙锁,互相阻塞对方的插入意向锁请求,最终导致死锁。
内容的提问来源于stack exchange,提问作者uzumas
相关产品推荐
相关产品推荐

