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是主键,则原记录被删,新记录插入 —— 即使只改了
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类似语法,但目前仍不支持)。
