这是 next-key lock 的退化形式,即对记录加排他锁(X)且对其前隙加间隙锁,常见于非唯一索引范围更新/删除时为防幻读而自动施加的混合锁。

“lock mode X locks rec but gap lock” 是什么锁?
这是 InnoDB 在死锁日志中对一种混合锁类型的简略描述,不是标准锁名,而是表示:当前事务持有的锁,既包含对记录本身(rec)的排他锁(X),又包含对该记录前一个间隙(gap)的间隙锁。
它常见于 UPDATE 或 DELETE 语句在非唯一索引(或无索引)上进行范围条件匹配时,InnoDB 为避免幻读而自动加上的组合锁。注意:这里 “rec but gap” 并非“记录锁 + 间隙锁”的简单叠加,而是指该锁在行为上同时覆盖记录和其前隙 —— 实际是 next-key lock 的一种退化表现,但因扫描过程中遇到已存在记录,导致间隙部分被“截断”。
典型触发场景包括:
- 执行
UPDATE t SET c=1 WHERE non_unique_idx > 10 AND non_unique_idx ,且索引值 15 已存在 - 在可重复读(RR)隔离级别下,
SELECT ... FOR UPDATE扫描非唯一二级索引范围 - 插入前做唯一性检查失败回滚后残留的锁状态(较少见)
为什么死锁日志里不直接写 “next-key lock”?
因为 InnoDB 日志只反映锁的最终生效形态,而非加锁意图。next-key lock 是理论模型(记录锁 + 间隙锁),但在实际加锁过程中,若目标记录已存在,InnoDB 可能只在该记录上加 X 锁,并在其前隙加间隙锁 —— 此时记录锁与间隙锁作用域不连续,日志就拆开描述为 “locks rec but gap lock”。
关键区别在于:
-
lock mode X locks rec→ 对具体聚集索引记录(或二级索引记录)加排他锁 -
lock mode X locks gap before rec→ 单独间隙锁(不锁记录) -
lock mode X locks rec but gap lock→ 同一操作引发两种锁,且间隙落在该记录之前(即:封锁区间为 (prev_rec, current_rec])
这个锁会阻塞哪些操作?
它会同时阻塞两类动作:
对记录本身的修改/删除:
- 其他事务尝试
UPDATE或DELETE同一条记录(聚集索引主键或二级索引项)→ 等待X锁
对间隙的插入:
- 其他事务尝试在该记录「之前」的间隙中插入新值(例如:当前记录索引值为 15,间隙为 (10, 15),则插入 12、14 都会被阻塞)→ 等待间隙锁
特别注意:它不阻塞在该记录「之后」插入(如插入 16),除非有另一个 next-key 锁覆盖 (15, next_rec]。
如何验证和排查这类锁?
最直接方式是结合 INFORMATION_SCHEMA.INNODB_TRX、INNODB_LOCKS(MySQL 5.7)或 INNODB_LOCK_WAITS(8.0+ 已移除,改用 performance_schema.data_locks)查看实时锁状态。
常用诊断步骤:
- 从死锁日志中提取
TRANSACTIONID 和涉及的TABLE、INDEX、HEAP NO - 查
SELECT * FROM performance_schema.data_locks WHERE LOCK_TRX_ID = 'xxx';,确认LOCK_MODE是否含X,REC_NOT_GAP或X,GAP组合 - 用
SELECT * FROM sys.innodb_lock_waits;(需启用 sys schema)快速定位阻塞链 - 检查对应 SQL 是否用了非唯一索引做范围查询 —— 这是最常见根源
真正容易被忽略的是:这种锁在 RR 级别下不会因语句结束立即释放,而是持续到事务提交或回滚;如果应用使用长事务或未显式提交,它可能长期持锁并成为死锁温床。










