mysql用insert ... on duplicate key update实现upsert,前提是表必须有primary key或unique索引,否则直接报错;语法为insert into t(c1,c2) values(v1,v2) on duplicate key update c2=values(c2),values(c2)引用本次插入值,不更新则该列保持原值。

MySQL里怎么用INSERT ... ON DUPLICATE KEY UPDATE做upsert?
这是最常用也最稳妥的方式,前提是表必须有PRIMARY KEY或UNIQUE索引,否则ON DUPLICATE KEY UPDATE不会触发。
常见错误是插入时字段没覆盖唯一约束列,比如只插name但唯一索引在email上,结果直接报Duplicate entry而不是走update分支。
- 语法必须写全:先
INSERT INTO table (a,b,c) VALUES (1,2,3),再跟ON DUPLICATE KEY UPDATE b=VALUES(b), c=VALUES(c) -
VALUES(col)引用的是本次INSERT中对应列的值,不是当前行旧值;别写成b=b+1除非真想累加 - 如果更新多列,每列都要显式列出,不能省略;漏掉某列会导致该列被设为默认值或
NULL - 注意
auto_increment字段:即使没在INSERT里指定,只要主键冲突,自增ID仍会+1(InnoDB行为)
REPLACE INTO看起来简单,为什么实际要慎用?
REPLACE INTO本质是DELETE + INSERT,不是原子更新。它会先删原记录再插新记录,导致外键级联、触发器、自增ID跳变等问题。
典型踩坑场景:表上有ON DELETE CASCADE关联子表,REPLACE会意外删掉子记录;或者用REPLACE更新带时间戳的字段,却忘了created_at也被重置了。
-
REPLACE返回的受影响行数可能是2(删1行+插1行),而INSERT ... ON DUPLICATE KEY UPDATE返回1表示更新、2表示插入 - 如果表没有主键/唯一索引,
REPLACE退化为普通INSERT,但语法上不报错,容易误判 - 无法像
ON DUPLICATE KEY UPDATE那样只更新部分字段——REPLACE必须提供所有非NULLABLE列的值
MySQL 8.0+ 的INSERT ... ON CONFLICT DO UPDATE能用吗?
不能。这是PostgreSQL语法,MySQL至今(8.0/8.4)都不支持ON CONFLICT。网上有些教程混用PG和MySQL示例,照抄会直接报错ERROR 1064。
如果你刚从PostgreSQL切过来,得立刻切换思维:ON DUPLICATE KEY UPDATE是MySQL唯一的标准upsert路径,别找替代关键字。
- 确认版本:运行
SELECT VERSION(),只要不是PostgreSQL就别试ON CONFLICT - 某些ORM(如Prisma)生成的SQL可能带
ON CONFLICT,需手动降级为MySQL兼容写法 - 社区有第三方工具模拟该语法,但底层仍是翻译成
INSERT ... ON DUPLICATE KEY UPDATE,无实质优势
并发环境下INSERT ... ON DUPLICATE KEY UPDATE真的原子吗?
是的,整个语句在单次执行中具备原子性,但要注意“原子”不等于“无竞争”。高并发下仍可能出现预期外的覆盖,比如两个事务同时发现无记录,都执行INSERT,后一个被判定为重复而触发UPDATE。
真正难处理的是业务逻辑依赖“首次插入才生效”的场景,比如发放唯一优惠码,这时单靠SQL不够,得加应用层锁或用SELECT ... FOR UPDATE先行占位。
- InnoDB在检测唯一冲突时会加gap lock,防止幻读,但锁范围可能比预期大,影响并发吞吐
- 如果UPDATE里包含函数调用(如
NOW()、UUID()),每次执行都会重新计算,不是事务开始时的快照值 - 避免在UPDATE子句里引用其他表字段,MySQL不支持
UPDATE t1 JOIN t2这种写法,会报错ERROR 1093
唯一索引定义是否覆盖全部业务唯一性判断,比语法选择更重要。写之前先盯紧那几个UNIQUE约束。











