想把一条数据存进表里:不存在就插入,存在就覆盖,很多人第一反应是 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。