唯一索引并发插入死锁的根本原因是交叉 insert 引发 s 锁循环等待:事务插入不同唯一值时,因唯一性检查需对同一索引间隙加 s 锁,而彼此等待对方释放,形成死锁;insert ignore 和 replace into 也无法规避该问题。

唯一索引冲突本身不直接导致死锁,但并发插入时对同一索引间隙的加锁顺序竞争会触发循环等待——根本原因是 InnoDB 在 RR 隔离级别下必须做磁盘级唯一性校验,从而提前申请 INSERT INTENTION LOCK 和 GAP LOCK,且无法跳过。
为什么 INSERT IGNORE 也拦不住死锁?
很多人误以为 INSERT IGNORE 是“无锁插入”,其实它只是把 Duplicate entry 错误转为静默忽略,加锁流程一模一样:
- 事务仍需先获取插入意向锁(
INSERT INTENTION LOCK),再尝试对唯一键值所在间隙加共享锁(S LOCK)做冲突检查 - 若另一事务已对该间隙持
X LOCK或正在插入相同前缀的值(如联合索引中部分字段为NULL),当前事务就会卡在lock_mode S waiting - 两个事务交叉等待对方释放 S 锁 → 死锁检测器介入,回滚其一
RR 隔离级别下 gap lock 范围怎么突然变大?
唯一索引的 gap lock 不是按“值”划界,而是按“索引页中当前存在的记录分布”动态计算。空表或稀疏数据会让 gap 覆盖极大区间:
- 建表后没数据,
INSERT INTO t(id, order_no) VALUES(1, 'ORD001')会锁住整个(-, +∞)区间(即全表 gap) - 此时另一个事务插
'ORD002',同样落在该 gap 内,必须等前一个事务释放 gap lock 才能继续 - 如果事务 A 插
'ORD002'、事务 B 插'ORD001',且都走INSERT IGNORE,就极易形成 S 锁互相等待
REPLACE INTO 为什么死锁风险翻倍?
REPLACE INTO 不是原子操作,绝大多数场景下等价于 DELETE + INSERT:
- 先申请目标行的
X LOCK执行删除(可能阻塞其他读写) - 再申请新位置的
INSERT INTENTION LOCK和对应 gap 的锁 - 同一个事务先后持有两处不同位置的锁,而其他事务可能以相反顺序申请——锁顺序错开,循环等待概率陡增
- 即使满足“最后一个唯一索引+无外键+无 trigger”的优化条件,也只跳过 delete,仍要走完整 insert 加锁路径
真正容易被忽略的是:**唯一索引死锁往往发生在“插入不同值”的场景下,而非重复值本身**。排查时别只盯着 Duplicate entry 错误,得看 SHOW ENGINE INNODB STATUS 里 lock_mode S waiting 的实际等待对象和 page no —— 那才是 gap 锁冲突的真实位置。











