REPLACE INTO 更易引发死锁,因其分两阶段执行:先INSERT失败回滚但不释放唯一索引上的LOCK_X,再DELETE+INSERT导致重复加锁;而INSERT ON DUPLICATE KEY UPDATE为单路径操作,锁少且确定。

REPLACE INTO 为什么加两次 LOCK_X 锁
它不是原子操作,而是被 MySQL Server 层拆成两个阶段执行:先尝试 INSERT,冲突后回滚聚集索引插入,再查出旧记录、DELETE + INSERT。关键点在于——第一阶段在唯一索引上加的 LOCK_X 不会随回滚释放,第二阶段又对同一行(UK + PK)再次加 LOCK_X 或 next-key lock。
这意味着一次冲突的 REPLACE INTO 实际持有 2~3 把 X 级别锁,跨二级索引和聚集索引。并发时,Session A 持有 b=8 的 next-key X 锁,Session B 卡在等待;A 进入第二阶段要申请 LOCK_INSERT_INTENTION,却与 B 已持有的 gap 锁范围重叠,死锁即刻触发。
为什么 INSERT ON DUPLICATE KEY UPDATE 更安全
它走的是单路径:“查 → 加一把 LOCK_X → 改或插”,没有“先插失败再补救”的中间状态。锁生命周期短、范围确定、无锁升级。
- 查到 UK 冲突:只对冲突行加一把
LOCK_X,后续UPDATE复用该锁 - 查不到冲突:等效普通
INSERT,只加必要行锁和 gap 锁 - 全程最多持两个行级锁,不会出现 insert intention 与 next-key lock 互相等待
多唯一索引场景下锁范围爆炸式扩大
当表有联合唯一索引 UNIQUE KEY (a,b) 和单列唯一索引 UNIQUE KEY (c) 时,REPLACE INTO 遇到任一 UK 冲突,都可能在多个索引上叠加 LOCK_X。而普通 INSERT 只在报错前检查一次、且只锁命中索引。
更隐蔽的是:即使你只更新 b 字段,只要 (a,b) 是 UK,InnoDB 就会在该二级索引和对应主键记录上都加锁;若还有外键或触发器,还会激活额外锁类型(如外键检查锁)。
自增列分配与锁顺序不可控加剧死锁概率
REPLACE INTO 即使最终没插入成功,也会为聚集索引分配新自增值(受 innodb_autoinc_lock_mode 影响)。这意味着并发事务的物理插入顺序和逻辑执行顺序不一致,导致锁申请顺序随机化。
例如两个事务同时执行 REPLACE INTO t(b) VALUES (8),各自拿到不同自增 ID 后,在二级索引 b=8 上竞争同一把 LOCK_X,但因聚集索引插入位置不同,又分别申请了不同区间的 gap 锁——这种非对称锁行为是死锁温床。
真正容易被忽略的,是 REPLACE INTO 在唯一索引检查阶段就强制加 LOCK_X,而不是像 INSERT 那样用 LOCK_S 允许并发读;这个设计决定了它从第一步起就比其他语句更容易卡住别人。


















