Next-Key Lock 本身不是死锁直接原因,而是其在RR级别下自动启用、范围加锁及共享间隙可重叠的组合行为,导致并发事务隐式形成S锁与Insert Intention X锁的循环等待。

Next-Key Lock 本身不是死锁的直接原因,而是它在 RR 隔离级别下「自动启用 + 范围加锁 + 共享间隙部分可重叠」的组合行为,让并发事务在无意识中形成循环等待。真正触发死锁的,是间隙锁(Gap Lock)与插入意向锁(Insert Intention Lock)之间的隐式冲突,尤其在唯一键冲突、范围查询或删除+插入混合操作中高频出现。
唯一键冲突时 Next-Key Lock 升级引发死锁
当多个事务并发 INSERT 相同唯一值(如 order_sn = 'SN_10086'),第一个事务成功插入并持物理行的 X 锁;其余事务因唯一约束失败,被迫申请该值所在区间的 Next-Key S 锁(本质是 (prev, current] 的共享间隙锁)。此时若第一个事务回滚,S 锁不释放,后续事务会互相阻塞在同一个间隙上——而它们又各自持有 Insert Intention Lock(一种特殊的 X 锁),等待获取该间隙内的插入权,最终形成 S 锁 ↔ Insert Intention X 锁的循环等待。
常见错误现象:
-
Deadlock found when trying to get lock; try restarting transaction (Error 1213)日志中显示两个事务都在等待lock_mode S locks gap before rec insert intention waiting - 表结构含
UNIQUE KEY,但业务未做前置去重或幂等校验
实操建议:
- 对唯一键插入场景,改用
INSERT IGNORE或ON DUPLICATE KEY UPDATE,避免事务因冲突进入锁等待 - 应用层加分布式锁或本地缓存(如 Redis)预判是否已存在,减少 MySQL 层面的冲突概率
- 禁止在事务中先
SELECT再INSERT判断——这会触发当前读 + Gap Lock,比直接INSERT更容易死锁
范围查询(WHERE ... FOR UPDATE)触发间隙锁死锁
RR 级别下,SELECT ... FOR UPDATE 或 UPDATE ... WHERE range 在二级索引上执行时,默认使用 Next-Key Lock,锁定满足条件的记录及其左侧间隙。例如索引值为 [5, 10, 15, 20],执行 SELECT * FROM t WHERE c > 10 FOR UPDATE,实际锁住的是 (10, 15] 和 (15, 20] 以及 (20, +∞) —— 这些间隙可能被其他事务同时用于插入,从而触发 Insert Intention Lock 等待。
使用场景:
- 订单分页查询后批量更新状态(如
WHERE create_time BETWEEN ? AND ?) - 按时间/金额范围做风控拦截(如
WHERE amount > 10000)
实操建议:
- 确认是否真需要“防止幻读”:若业务允许短暂不一致(如后台统计),可降级为
READ COMMITTED,彻底禁用 Gap Lock - 用主键或唯一索引精确查询替代范围查询,例如把
WHERE status = 1 AND create_time > '2026-09-20'拆成先查 ID 列表,再用WHERE id IN (...)批量更新 - 避免在非必要字段(如无索引的
varchar列)上做范围FOR UPDATE,否则会升级为全表扫描 + 表级锁风险
DELETE + INSERT 同一范围导致间隙锁竞争
典型场景是“先删旧数据,再插新数据”,比如同步配置表或工单快照。若两个事务执行相同逻辑:DELETE FROM t WHERE tenant_id = 123 → INSERT INTO t (...) VALUES (...),前者会加 Next-Key Lock 覆盖 tenant_id = 123 对应的所有间隙,后者则需在相同间隙内申请 Insert Intention Lock,极易形成等待链。
参数差异影响:
- 如果
tenant_id是普通索引(非唯一),DELETE会锁整个匹配区间,而非单行 - 如果
tenant_id是联合索引首列(如(tenant_id, version)),且WHERE只含tenant_id,仍会锁范围
实操建议:
- 改用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE替代删+插,减少锁持有时间 - 确保
tenant_id字段有唯一约束或主键覆盖,让 InnoDB 能将 Next-Key Lock 退化为 Record Lock - 若必须删+插,把
DELETE改为基于主键的批量删除(先SELECT id,再DELETE FROM t WHERE id IN (...)),避免范围扫描
最易被忽略的一点:Gap Lock 的“共享性”具有欺骗性——多个事务可以同时持有同一间隙的 S 锁,但这不代表安全;一旦其中任一事务尝试插入,就会触发 Insert Intention Lock,而这个锁与所有已存在的 Gap S 锁互斥。死锁往往发生在“大家都以为只是读,结果谁都插不进去”的瞬间。


















