REPLACE INTO 是危险操作,实际执行 DELETE + INSERT,导致自增主键变更、外键失败、触发器四次调用、binlog放大、并发覆盖及主备 AUTO_INCREMENT 不一致;应改用 INSERT ... ON DUPLICATE KEY UPDATE。

REPLACE INTO 不是“安全替换旧数据”的便捷方法,而是带有明确副作用的危险操作。它在绝大多数主键冲突场景下都不该被选用。
REPLACE INTO 实际执行的是 DELETE + INSERT,不是更新
MySQL 没有原地替换语义:REPLACE INTO 会先根据主键或任意唯一索引(比如 UNIQUE KEY name)定位已有行,然后执行 DELETE,再执行 INSERT。这意味着:
- 自增主键一定会变——哪怕只改一个字段,新记录也会拿到新的
AUTO_INCREMENT值,旧 ID 永久丢失 - 如果表有外键约束且未设
ON DELETE CASCADE,操作直接失败并报错ERROR 1452 (23000) - 触发器会触发四次:
BEFORE DELETE、AFTER DELETE、BEFORE INSERT、AFTER INSERT - binlog 记录为两条事件(
DELETE+INSERT),在主从复制中放大延迟和回放风险
并发下 REPLACE INTO 会丢失中间变更
多个线程同时对同一主键执行 REPLACE INTO,会出现竞态丢失,不是“最终一致”,而是数据覆盖。
- A 线程想把
status = 'pending'改成'processing' - B 线程想把
retry_count加 1,但VALUES中没显式带status - 两者都成功完成
DELETE后,B 的INSERT覆盖了 A 的status字段——A 的修改彻底丢失 -
REPLACE INTO完全不保证“读取 → 计算 → 写入”原子性
主备不一致:AUTO_INCREMENT 在从库不会同步增长
当表含唯一键(非主键)时,REPLACE INTO 在主库走 DELETE + INSERT 流程,AUTO_INCREMENT 递增;但从库 binlog 回放时,MySQL 将其重写为 UPDATE,而 UPDATE 不推进 AUTO_INCREMENT 值。
- 主库
AUTO_INCREMENT=4,从库仍为2 - 主备切换后,新主库用旧的低自增值插入,可能覆盖已有数据或触发
Duplicate entry错误 - 这种差异无法通过
SHOW SLAVE STATUS发现,只能靠定期校验AUTO_INCREMENT值
真正该用的替代方案是 INSERT ... ON DUPLICATE KEY UPDATE
它才是真正原子、可控、可预测的冲突处理方式。
- 只更新指定字段,不改变主键值,不触发 DELETE 相关逻辑
- 支持表达式,比如
retry_count = retry_count + 1,避免读-改-写竞态 - 影响行数稳定:插入为 1 行,更新为 2 行(
Rows matched: 1 Changed: 1) - 示例:
INSERT INTO users (id, name, retry_count) VALUES (1, 'Alice', 1) ON DUPLICATE KEY UPDATE name = VALUES(name), retry_count = retry_count + 1
REPLACE INTO 就可能触发删除+插入,而业务往往只关注主键,却忘了 UNIQUE KEY email 或 UNIQUE KEY token 同样会引爆这个行为。


















