REPLACE 语句本质是“删+插”而非原地更新:先按主键或唯一索引删除旧记录,再插入新记录,导致自增ID变化、触发器和外键级联各执行两次;必须有主键或UNIQUE索引,否则等同INSERT;推荐优先使用INSERT ... ON DUPLICATE KEY UPDATE实现安全更新。

REPLACE 语句本质是「删+插」,不是 UPDATE
MySQL 的 REPLACE 不是原地修改,而是先根据主键或唯一索引尝试查找匹配行;如果存在,就先 DELETE 掉旧记录,再 INSERT 新记录。这意味着自增 ID 会变化、触发器会执行两次(一次 DELETE,一次 INSERT)、外键级联动作也会被触发两次。
常见误用场景:想更新某字段却用了 REPLACE,结果发现 ID 增了、历史记录断了、binlog 里多了一条删除日志。
- 必须确保表有主键或至少一个
UNIQUE索引,否则REPLACE等价于INSERT - 若不想改变自增 ID,应改用
INSERT ... ON DUPLICATE KEY UPDATE - 事务中使用
REPLACE要注意锁行为:它会对冲突的唯一索引值加 next-key lock,可能比普通 INSERT 更容易引发死锁
REPLACE INTO 语法与 INSERT 几乎一致,但语义完全不同
REPLACE INTO 支持和 INSERT INTO 相同的写法:列名列表、VALUES、SELECT 子查询等。但只要发生唯一键冲突,就会走删除再插入逻辑。
示例:
REPLACE INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com');
如果 id=1 已存在,且 id 是主键,则原记录被删,新记录插入 —— 即使只改了 email,name 字段也会被覆盖为新值(不会保留旧值)。
- 不支持部分字段更新,所有未显式指定的列都会设为默认值或 NULL
- 用
SELECT构造数据时,同样触发完整替换:例如REPLACE INTO t1 SELECT * FROM t2 WHERE id=5,若 t2.id=5 在 t1 中已存在,整行被替换 - 批量
REPLACE比单条更耗资源,因为每行都可能触发 delete + insert 两阶段操作
替代方案:INSERT ... ON DUPLICATE KEY UPDATE 更安全可控
当目标只是“有则更新、无则插入”,INSERT ... ON DUPLICATE KEY UPDATE 是更推荐的选择。它在检测到唯一键冲突时只执行 UPDATE,不改动主键、不触发 DELETE 相关逻辑。
示例:
INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@new.com') ON DUPLICATE KEY UPDATE email = VALUES(email), name = VALUES(name);
-
VALUES(col)表示本次 INSERT 中该列的值,避免重复写字面量 - 可以只更新部分字段,其余字段保持原值不变
- 支持在 UPDATE 子句中使用表达式,比如
view_count = view_count + 1 - 若表有多个唯一索引,只要任一索引冲突即触发 UPDATE,但无法指定按哪个索引判断
REPLACE 在 REPLACE INTO SELECT 场景下容易忽略主从延迟和 binlog 格式影响
当用 REPLACE INTO ... SELECT 批量同步数据时,主库执行的是 delete + insert,从库重放时也会完全复现这个过程。在 STATEMENT 格式 binlog 下,若 SELECT 子查询含非确定性函数(如 NOW()、RAND()),可能导致主从不一致。
- 建议将 binlog_format 设为
ROW,确保变更内容精确复制 - 大表上执行
REPLACE INTO t SELECT ...可能导致长事务、锁表时间久,考虑分批或改用临时表 + 重命名 - 某些 ORM(如 Django 的
bulk_create(..., update_conflicts=...))底层会生成ON DUPLICATE KEY UPDATE,而非REPLACE,注意框架封装带来的行为差异
真正需要 REPLACE 的场景其实很少:比如强制刷新缓存表、重建聚合快照、或业务明确接受 ID 变更和双触发器。大多数“替换插入”需求,其实更适合用 INSERT ... ON DUPLICATE KEY UPDATE 或 MERGE(MySQL 8.0.19+ 的 INSERT ... ON CONFLICT 类似语法,但目前仍不支持)。










