replace into 实际执行 delete + insert,非更新操作;触发唯一键冲突时删除旧记录并插入新记录,导致自增id跳变、外键级联、触发器重复执行及未指定字段被重置为默认值,多唯一索引还可能误删多行;正确替代方案是 insert ... on duplicate key update。

REPLACE INTO 实际执行的是 DELETE + INSERT
它根本不是“更新”,而是先删旧行、再插新行。只要触发了唯一键(主键或任意唯一索引)冲突,MySQL 就会走这套流程。REPLACE INTO 语句本身不关心你改了几个字段,也不保留原记录的 id 值——哪怕你显式指定了 id 字段,只要发生冲突,旧记录就被物理删除,新插入的记录会申请一个新的自增 ID(除非你显式传入已存在的 id 值且未触发其他唯一索引冲突)。
自增ID跳变在主从环境中尤其危险
主库执行 REPLACE INTO 后,AUTO_INCREMENT 值会立即递增;但从库在 ROW 格式 binlog 下只回放数据变更(即最终状态),不会同步这个自增值。结果就是:
- 主库
AUTO_INCREMENT = 1000,从库仍是998 - 一旦发生主从切换,新主库(原从库)继续插入时,可能生成
id = 999或1000,而该值已在表中存在 → 直接报Error 1062: Duplicate entry 'xxx' for key 'PRIMARY' - 这种不一致无法通过常规监控发现,只有切主后才暴露
你以为只改一个字段,其实全字段被重写
REPLACE INTO 没有“部分更新”概念。如果你写的是:
REPLACE INTO users (uid, email) VALUES (123, 'new@example.com');
而表里已有 uid=123 的记录,那么:
- 原记录被 DELETE,所有字段(包括
created_at、status、updated_at等)一并消失 - 新插入的记录中,未显式指定的字段会使用默认值(如
NULL或DEFAULT),极易造成数据丢失 - 如果表有多个唯一索引,还可能意外删掉多条匹配的记录(比如
uid和phone都唯一,且两条不同记录分别命中其中一个)
真正需要“存在则更新”的场景,请用 ON DUPLICATE KEY UPDATE
它才是 MySQL 原生支持的原子更新语义:
- 只修改指定字段,其余字段保持不变
- 不触发 DELETE 和 INSERT 触发器,只触发 UPDATE 相关触发器
-
AUTO_INCREMENT不变(除非你显式更新了主键值) - binlog 记录为单条
UPDATE事件,主从自增值严格一致 - 并发安全:不会因竞态导致字段覆盖丢失(比如 A 改
status、B 改retry_count,两者可同时生效)
真正难处理的,是那些没建唯一索引却误以为 REPLACE INTO 能去重的表——它连“替换”都不会触发,只会默默重复插入。











