MySQL的INSERT ... ON DUPLICATE KEY UPDATE实现“有则更新、无则插入”必须依赖主键或唯一索引,否则无法触发更新;VALUES(col)引用本次插入值,用于安全批量操作;其原地更新特性优于REPLACE INTO的删插机制。

MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE 实现“有则更新、无则插入”是最直接、最安全的方式,前提是表必须有主键或唯一索引(单列或多列联合)。
为什么必须有主键或唯一索引
该语法不是靠“查一遍再决定”,而是靠 MySQL 内部的约束冲突机制触发更新。没有 PRIMARY KEY 或 UNIQUE 索引,ON DUPLICATE KEY UPDATE 根本不会生效——它连“哪条算重复”都无从判断。
常见错误现象:Query OK, 1 row affected 但始终只插入不更新,或者报错 ERROR 1062: Duplicate entry ... for key '...' 却没走 update 分支,大概率是索引缺失或类型不匹配(比如 VARCHAR 字段没加 UNIQUE,或联合索引漏了某列)。
- 唯一索引字段内容区分大小写(取决于排序规则,如
utf8mb4_0900_as_cs) - NULL 值在唯一索引中不参与冲突判定:两个
NULL不算重复 - 如果同时存在主键和多个唯一索引,冲突优先按主键判断;主键不冲突时,才检查唯一索引
VALUES() 函数怎么用才不翻车
在 ON DUPLICATE KEY UPDATE 子句里,VALUES(column) 引用的是本次 INSERT 中对应列的原始值,不是当前行旧值。这是避免硬编码、支持批量操作的关键。
示例语句:
INSERT INTO users (username, email, score)
VALUES ('alice', 'a@b.com', 100)
ON DUPLICATE KEY UPDATE
email = VALUES(email),
score = score + VALUES(score);注意点:
- 不能写
email = 'a@b.com'—— 批量插入时每行值不同,硬编码会全更新成同一个值 -
score = score + VALUES(score)是安全的累加,旧值 + 新插入值;写成score = VALUES(score)就是覆盖 -
VALUES()只能在ON DUPLICATE KEY UPDATE里用,不能用于其他子句(如WHERE)
批量插入更新时的字段映射陷阱
批量执行时(多组 VALUES),ON DUPLICATE KEY UPDATE 对每一行独立生效,但更新逻辑是统一的。容易忽略的是字段顺序与 VALUES() 的绑定关系。
错误写法(字段顺序错位):
INSERT INTO users (id, username, email) VALUES (1,'a','a@x'), (2,'b','b@y') ON DUPLICATE KEY UPDATE username = VALUES(email); -- ❌ 把 email 值赋给了 username
正确做法:
- 确保
INSERT列名列表与后面每组VALUES的顺序严格一致 -
VALUES(col)中的col必须出现在INSERT列表中,否则报错Unknown column 'xxx' in 'field list' - 如果要更新未出现在
INSERT列表中的字段(比如updated_at),可直接写表达式:updated_at = NOW()
和 REPLACE INTO 的核心区别在哪
别图省事用 REPLACE INTO 替代 ON DUPLICATE KEY UPDATE,它们行为完全不同:
-
REPLACE INTO是“删+插”:先 DELETE 旧行,再 INSERT 新行 → 自增 ID 会变、外键关联可能断裂、触发器会执行两次 -
ON DUPLICATE KEY UPDATE是原地 UPDATE:ID 不变、外键安全、只触发一次UPDATE触发器 - 当表有多个唯一索引且部分冲突时,
REPLACE INTO一定删一行;而ON DUPLICATE KEY UPDATE可能因冲突路径不同导致死锁(尤其高并发下),需配合重试或降低隔离级别
真正需要“upsert”语义时,ON DUPLICATE KEY UPDATE 是默认首选;只有明确接受 ID 变更、且无外键依赖时,才考虑 REPLACE INTO。


















