mysql并发插入死锁由插入意向锁、间隙锁和唯一约束冲突共同引发,根本原因是insert前的隐式加锁行为;排查需依赖show engine innodb status,修复应优先优化索引与批量插入。

MySQL并发插入死锁不是配置问题,而是由插入意向锁(INSERT intention lock)、间隙锁(GAP lock)和唯一约束冲突共同触发的典型现象。InnoDB 会自动回滚其中一个事务并报错 Deadlock found when trying to get lock,但频繁发生说明业务逻辑或索引设计存在隐患。
为什么 INSERT 会触发死锁?
根本原因不在“插入本身”,而在插入前的隐式加锁行为:
-
INSERT执行前,InnoDB 先在目标位置的间隙上加一个INSERT intention gap lock(插入意向间隙锁),表示“我打算往这个间隙插” - 如果该间隙已被其他事务用
SELECT ... FOR UPDATE或UPDATE ... WHERE range锁住(比如WHERE id > 100),就会等待 - 若两个事务分别持有一个间隙的
Next-Key Lock(记录锁 + 间隙锁),又同时想插入对方锁定的间隙,就形成循环等待 - 更常见的是:唯一索引冲突时,InnoDB 会给冲突行加
S(共享)锁,而非X(排他)锁;多个事务同时插入相同唯一值(如重复tenant_id),就会因争抢同一行的S锁而死锁
排查死锁必须看 SHOW ENGINE INNODB STATUS
仅靠应用日志看不出本质。每次死锁后立即执行该命令,重点盯三块:
-
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:—— 当前事务卡在哪条 SQL、等什么锁(注意锁类型是X、S还是GAP) -
*** (2) HOLDS THE LOCK(S):—— 它已经持有哪些锁(常含next-key lock on index xxx) -
*** WE ROLL BACK TRANSACTION (1)—— 哪个事务被选为牺牲者(InnoDB 按代价最小原则选)
如果看到 lock_mode S locks rec but not gap waiting 或 lock_mode X locks gap before rec insert intention waiting,基本可断定是唯一键冲突或间隙锁竞争。
INSERT 死锁的四个实操修复点
不改代码只调参数治标不治本。以下措施按优先级排序:
-
强制走唯一索引 + 避免批量 for 循环插入:确认
INSERT的WHERE条件字段(如tenant_id)有UNIQUE索引;把应用层for循环单条INSERT改成INSERT ... VALUES (...), (...), (...)批量语句,减少事务数和锁交互次数 -
用
INSERT IGNORE或ON DUPLICATE KEY UPDATE替代先查后插:避免显式SELECT ... FOR UPDATE引入额外间隙锁;INSERT IGNORE在冲突时静默跳过,不抛锁异常 -
对热点唯一键做预占位(如 UUID 前缀分片):若用
UUID作主键,InnoDB 页分裂易加剧间隙锁冲突;可改用COMBINE UUID(时间戳前缀)或REPLACE INTO+ 应用层幂等控制 -
临时降级隔离级别(慎用):在明确无幻读风险的插入场景,可设
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,关闭GAP lock;但需验证是否影响其他查询逻辑
最容易被忽略的细节
死锁日志里出现 lock_mode S locks rec but not gap 时,很多人以为是“读锁不冲突”,其实这是唯一键冲突后的共享锁——它和另一个事务的插入意向锁(insert intention)是互斥的。此时哪怕只并发两个请求,只要撞上同一个唯一值,就稳死锁。别迷信“只有三个以上才死锁”的说法,官网文档明确写了双事务冲突场景。真正要盯的,永远是 SHOW ENGINE INNODB STATUS 里那几行锁描述,而不是错误码本身。











