想把一条数据存进表里:不存在就插入,存在就覆盖,很多人第一反应是 mysql replace into。它确实能干这个活,但行为和直觉有点差别,用之前最好把机制搞清楚。

语法和 insert 几乎一样:

REPLACE INTO user_score (uid, subject, score) VALUES (1001, 39, 88);

执行时 MySQL 先按主键或唯一索引找冲突:没有冲突,就当普通 insert 插入;一旦 uid 加 subject 组成的唯一键撞上了,旧行会被整行删除,新行再插入。所以它叫 replace,本质是 delete 加 insert 的组合,而不是逐字段更新。

三个容易踩的坑

  • 没写进 VALUES 的列会丢。旧行里 remark 字段本来有值,这条语句没带这个字段,新行里它就回到默认值,数据等于被抹了。
  • 自增 id 会跳。每次删除重插都分配新的自增值,业务要是拿 id 做关联,外键关系会断。
  • 删除和插入触发器都会触发,受影响行数显示 2,做审计逻辑的要留意。

只想更新部分字段怎么办

多数场景更合适的写法是 INSERT 后面跟 ON DUPLICATE KEY UPDATE,冲突时只更新指定字段:

INSERT INTO user_score (uid, subject, score) VALUES (1001, 39, 88) ON DUPLICATE KEY UPDATE score = 88;

这样 remark、创建时间这些字段原样保留,自增 id 也不动。MySQL 5.7 和 8.0 对这两条语句的行为一致,迁移不用担心语法差异。批量导入对账数据时,如果整行都以新数据为准,replace into 反而更省事,一条语句顶两条。

一句话判断:整行覆盖、表里没有别的字段依赖旧行,用 replace into 没问题;要保留未提交的字段,或者有外键引用 id,就走 ON DUPLICATE KEY UPDATE。