on duplicate key update 仅在主键或唯一索引(含复合)冲突时触发更新;无唯一约束、索引不匹配或列未出现在 values 中均不更新;受影响行数为2表示更新,1为插入,0为值未变。

能用,但必须满足唯一约束触发条件,否则就只是普通 INSERT。
ON DUPLICATE KEY UPDATE 什么情况下会真正触发更新?
它只响应主键或唯一索引(包括复合唯一索引)的冲突,不是“只要数据重复就更新”。常见误判场景:
- 表没建任何
PRIMARY KEY或UNIQUE索引 → 语句执行成功,但永远不走 UPDATE 分支 - 想按
username去重更新,但只给id加了主键,username没加UNIQUE→ 冲突检测失效 - 复合唯一索引是
(user_id, date),但 INSERT 只提供user_id→ 不匹配索引前缀,不触发
验证是否生效最直接的方式是看返回的受影响行数:1 是插入,2 是更新,0 是更新值与原值完全一致(MySQL 默认行为)。
VALUES() 函数怎么用才不出错?
VALUES(col_name) 是引用当前 INSERT ... VALUES 子句中对应列的值,不是查表里的旧值。容易写错的地方:
- 列名拼错,比如写成
VALUES(user_name)但实际字段叫username→ 报错Unknown column 'user_name' in 'field list' - 在
ON DUPLICATE KEY UPDATE里用了没出现在VALUES中的列 →VALUES()返回NULL,可能导致意外覆盖(如status = VALUES(status)把非空状态设成NULL) - 批量插入多行时,
VALUES(col)对每一行分别取值,无需额外逻辑 —— 这是它比手写UPDATE更安全的核心优势
推荐写法示例:
INSERT INTO user_stats (user_id, login_count, last_login) VALUES (123, 1, NOW()), (456, 1, NOW()) ON DUPLICATE KEY UPDATE login_count = login_count + VALUES(login_count), last_login = VALUES(last_login);
为什么有时更新后自增 ID 跳变了?
这是 InnoDB 的固有行为:即使最终走了 UPDATE 分支,INSERT ... ON DUPLICATE KEY UPDATE 仍会预先申请一个 AUTO_INCREMENT 值。如果表只有主键是自增列,而你又依赖连续 ID 做业务(比如分页、导出序号),这个跳变就会暴露。
- 不是 bug,是设计使然 —— MySQL 需要保证并发插入时 ID 分配不冲突
- 无法关闭,也不能通过
SET innodb_autoinc_lock_mode = 0规避(该参数影响的是批量插入模式) - 若业务强依赖连续 ID,应避免用此语法,改用显式
UPDATE ... WHERE+INSERT IGNORE组合
多个唯一索引共存时要特别小心
当一张表有多个唯一约束(比如同时有 email UNIQUE 和 phone UNIQUE),ON DUPLICATE KEY UPDATE 的行为会变得不确定:
- 冲突由哪个索引触发,UPDATE 就按那个索引的等值条件执行
WHERE(例如命中email冲突,就等价于WHERE email = ?) - 但如果某行同时满足多个唯一索引的冲突条件(比如新数据的
email和phone都已存在),MySQL 只选其中一个索引处理,具体选哪个不保证 - 官方文档明确建议:“In general, you should try to avoid using an ON DUPLICATE KEY UPDATE clause on tables with multiple unique indexes.”
最稳妥的做法是确保业务上只依赖一个明确的唯一键(主键或单个唯一索引)作为 UPSERT 的判断依据,其余字段去重逻辑由应用层控制。











