一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

为什么MySQL的Replace Into语句相比Insert更容易产生锁

时间:2026-07-16 08:16:52 编辑:袖梨 来源:一聚教程网

Replace Into 比 Insert 多加 LOCK_X 记录锁,因其在唯一索引检查阶段即加排他锁,而 Insert 加共享锁;冲突时 Replace Into 不立即释放锁,而是执行删除再插入,导致锁持有时间长、范围广、易死锁。

Replace Into 为什么比 Insert 多加一把 LOCK_X 记录锁

因为 Replace Into 在唯一索引检查阶段就加的是排他锁(LOCK_X),而普通 Insert 加的是共享锁(LOCK_S)。这个差异直接放大了锁冲突概率。

具体来说:当遇到唯一键(UK)冲突时,InnoDB 层在检查 b=8 是否已存在时,Insert 会加 LOCK_S | LOCK_REC_NOT_GAP,允许其他事务并发读;Replace Into 则强制加 LOCK_X,禁止任何其他事务对同一记录加锁——哪怕只是读。

  • Insert 检查冲突 → 加 LOCK_S → 冲突则报错退出,锁很快释放
  • Replace Into 检查冲突 → 加 LOCK_X → 冲突后不立即释放,而是回滚聚集索引插入、再尝试 delete + insert,全程持有该 LOCK_X
  • 如果多个 Replace Into 并发争抢同一个唯一值(如都插 b=8),第一个拿到 LOCK_X,其余全卡在等待,极易触发死锁

Replace Into 的两阶段加锁导致锁无法及时释放

Replace Into 不是原子操作,它被拆成「先试插 → 失败 → 再删再插」两个阶段,但第一阶段加的锁不会回退,要等到整个语句执行完才释放。

这意味着:即使你只是想更新一条已存在的记录,MySQL 仍会先尝试插入一条新行(带自增 ID),这个动作会申请插入意向锁(LOCK_INSERT_INTENTION)和间隙锁(LOCK_GAP),而这些锁在冲突发生后并不会立刻释放,而是延续到第二阶段的 delete + insert 完成。

  • Session1 执行 replace into t(b) values (8) → 在 b=8 上加 LOCK_X,同时在 (8,100) 区间加 next-key 锁
  • Session2 同样执行 → 卡在等待 LOCK_X,但它自己也已持有了部分 gap 锁
  • 双方互相等待对方释放锁,死锁形成

Replace Into 删除旧记录会触发额外锁类型

Insert 只涉及插入路径,Replace Into 在冲突路径下必须走 delete 流程,这会激活更多锁行为:删除聚集索引行、标记二级索引项、清理 undo log 等,每一步都可能申请新锁。

特别是当表有多个唯一索引时,Replace Into 需要依次检查每个 UK,只要有一个冲突,就可能在多个索引上叠加 LOCK_X;而 Insert 只会在报错前检查一次,且只锁命中索引。

  • 有联合唯一索引 UNIQUE KEY (a,b) 和单列 UNIQUE KEY (c) 时,Replace Into 可能同时在两处加 LOCK_X
  • Delete 操作本身会获取聚集索引记录上的 LOCK_X,并可能触发外键检查锁(如果有关联外键)
  • 自增列(AUTO_INCREMENT)在 Replace Into 中仍会分配新值(即使最终没用上),受 innodb_autoinc_lock_mode 影响,可能引入表级锁竞争

Replace Into 在高并发下更容易暴露锁粒度问题

Insert 是纯新增行为,只要不撞唯一键,基本只锁自己那行;Replace Into 的语义本质是「覆盖」,它的锁范围天然更宽——既要防止别人插入相同唯一值(gap lock),又要阻止别人修改或删除当前冲突行(record lock),还要为后续 delete + insert 预留空间(insert intention lock)。

这种组合让 Replace Into 在热点唯一键(比如订单号、设备 ID、用户手机号)场景下,锁等待链极容易拉长。尤其当应用层未控制并发量、又没加重试机制时,一个失败的 Replace Into 可能卡住后续几十个请求。

  • 并发量 > 50 QPS 且唯一键重复率 > 20% 时,Deadlock found when trying to get lock 出现频率显著上升
  • 使用 INSERT ... ON DUPLICATE KEY UPDATE 替代后,同样场景下死锁几乎消失,因为它是原地 update,不触发 delete 阶段的锁扩张
  • 若必须用 Replace Into,请确保表只有 1 个唯一索引,并关闭外键、禁用 delete trigger,否则锁路径会进一步复杂化
实际线上出问题时,往往不是单条语句慢,而是某条 Replace Into 卡住后,把整个连接池拖垮——这点比锁本身更隐蔽。

热门栏目