主键不连续会触发间隙锁死锁,因其导致InnoDB在空缺区间(如(2,5)、(5,8))加Next-Key Lock,而并发INSERT易争抢同一间隙,结合未走索引、锁升级或无序插入等行为引发死锁。

为什么主键不连续会触发间隙锁死锁
主键不连续本身不会直接导致死锁,但它是间隙锁(Gap Lock)被激活的典型诱因。MySQL 5.7 默认隔离级别是 REPEATABLE READ,InnoDB 对范围查询或 SELECT ... FOR UPDATE 会自动使用 Next-Key Lock(记录锁 + 间隙锁)。当主键存在空缺(比如有 1, 2, 5, 8),InnoDB 就会在 (2,5)、(5,8) 这些“间隙”上加锁——而这些间隙恰好是并发 INSERT 最可能争抢的位置。
查清当前语句是否命中间隙锁
别猜,先看执行计划和锁行为:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 用
EXPLAIN FORMAT=TRADITIONAL检查 WHERE 条件是否走了索引;没走索引 → 全表扫描 → 行锁升级为表锁 → 死锁风险陡增 - 执行
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;查活跃事务,结合SHOW ENGINE INNODB STATUS\G中的LATEST DETECTED DEADLOCK段,确认锁类型是不是gap lock或next-key lock - 特别注意日志里类似
lock_mode X locks gap before rec的描述——这就是间隙锁在作祟
避免间隙锁死锁的实操手段
核心思路不是消灭间隙锁(它防幻读必不可少),而是让并发插入避开冲突间隙,或让锁竞争可预测:
- 对主键字段显式加唯一索引(哪怕它已是主键):防止因索引缺失导致优化器误判,强制走聚簇索引,减少隐式锁升级
- 批量插入时统一按主键升序排序再执行:比如插入
(5, 3, 8)改为(3, 5, 8),避免事务 A 锁(2,5)、事务 B 同时锁(5,8)后又反向请求对方间隙 - 用
INSERT ... ON DUPLICATE KEY UPDATE替代先SELECT再INSERT:减少事务中锁的跨度,避免SELECT ... FOR UPDATE在空缺区间提前加间隙锁 - 若业务允许,临时降级隔离级别为
READ COMMITTED:该级别下 InnoDB 不使用间隙锁(只用记录锁),但需确认是否引入幻读风险
最容易被忽略的细节
间隙锁死锁往往藏在“看似安全”的地方:比如一个 UPDATE 语句 WHERE 条件是 id > 100 AND id ,即使表里当前只有 <code>id=101 和 id=199,InnoDB 仍会对 (101,199) 整个间隙加锁——此时任何 INSERT INTO ... VALUES (150) 都会被阻塞,若两个事务交叉请求,立刻死锁。不要依赖“当前数据少就没事”,要盯住索引结构和语句的锁覆盖范围。

















