gap lock本身不直接导致死锁,但它是mysql在rr隔离级别下并发insert时死锁的关键催化剂:事务同时在相同间隙加gap lock后,又互相等待对方释放该锁以获取插入意向锁,形成循环等待。

Gap Lock 本身不直接导致死锁,但它是 MySQL 在 REPEATABLE READ 隔离级别下形成死锁的关键催化剂——尤其在并发 INSERT 或 INSERT ... ON DUPLICATE KEY UPDATE 场景中,它让多个事务“在看不见对方的情况下,同时卡在同一个间隙里”。
Gap Lock 在 RR 下如何被自动触发
只要 SQL 是当前读(SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT),且 WHERE 条件落在**非唯一索引**或**无索引字段**上,InnoDB 就会用 Next-Key Lock(Record Lock + Gap Lock)锁定记录及其前后间隙。即使你只查一条记录,比如:
SELECT * FROM orders WHERE status = 'pending' FOR UPDATE;
若 status 是普通索引(非唯一),InnoDB 不仅锁住所有 status = 'pending' 的行,还会锁住这些值之间的所有间隙——比如现有 status 值为 'done'、'pending'、'canceled',那么 'pending' 对应的间隙可能被解释为 ('done', 'pending') 和 ('pending', 'canceled') 两个开区间,其他事务想插入新状态(如 'retrying')就可能被阻塞。
关键点:
- 唯一索引(含主键)上的等值查询(
=)只加 Record Lock,不加 Gap Lock; - 非唯一索引、范围查询(
>、BETWEEN)、IS NULL等,都会激活 Gap Lock; -
INSERT语句本身会先申请「插入意向锁(Insert Intention Lock)」,这是一种特殊的 Gap Lock,与普通 Gap Lock 冲突——这就是死锁温床。
并发 INSERT 死锁的典型链路
两个事务几乎同时执行:
INSERT INTO users (email, name) VALUES ('a@b.com', 'A') ON DUPLICATE KEY UPDATE name = VALUES(name);
假设 email 是唯一索引,但表中尚无该 email 记录。此时 InnoDB 的行为是:
- 事务 T1:扫描
email索引,发现无匹配记录 → 在「该 email 应该插入的位置」加 Gap Lock(防止别人插进来)→ 申请插入意向锁 → 等待 Gap Lock 释放(但自己正持有它); - 事务 T2:做同样操作,在同一间隙加 Gap Lock,并申请插入意向锁;
- 结果:T1 持有 Gap Lock A,等待插入意向锁 A;T2 持有 Gap Lock A,等待插入意向锁 A → 双方都在等对方释放 Gap Lock,形成循环等待。
注意:这种死锁在 READ COMMITTED 下不会发生,因为 RC 级别完全禁用 Gap Lock(只保留 Record Lock)。
为什么 show engine innodb status 显示 “lock_mode X locks gap before rec insert intention waiting”
这是最典型的 Gap Lock 并发插入死锁日志特征。拆解这串信息:
-
lock_mode X:排他锁; -
locks gap before rec:锁的是记录前的间隙(Gap Lock); -
insert intention waiting:当前事务正等待获取插入意向锁,但它被另一个事务持有的 Gap Lock 挡住了。
也就是说:两个事务都已成功加了 Gap Lock,又都卡在申请插入意向锁这一步——而插入意向锁与 Gap Lock 是互斥的(官方文档明确定义),于是 MySQL 检测到环形等待,回滚其中一个。
真正容易被忽略的细节
Gap Lock 导致的死锁,往往不是出现在你写的 SELECT ... FOR UPDATE 上,而是藏在 ORM 自动生成的语句、隐式事务、或者你以为“只是插入”的 ON DUPLICATE KEY UPDATE 里。更隐蔽的是:联合唯一索引如果只用部分列查询(如 WHERE a = ?,但索引是 (a, b)),InnoDB 仍会加 Gap Lock——因为它无法精确定位到单条记录。
修复时别只盯着 SQL 改写,优先确认三件事:隔离级别是否真需 RR、索引类型是否覆盖查询条件、INSERT 是否真的必须用 ON DUPLICATE KEY UPDATE 而非先 SELECT 再判断——后者虽多一次网络往返,但可彻底避开 Gap Lock 竞争。











