ON DUPLICATE KEY UPDATE 仅在唯一索引(UNIQUE/PRIMARY KEY)冲突时生效,非索引字段重复不触发;UPDATE中用VALUES(col)取新值、列名取旧值;与REPLACE INTO的“删+插”本质不同,后者会导致ID跳变和外键级联风险。

ON DUPLICATE KEY UPDATE 什么时候生效
它只在插入操作触发了唯一约束(UNIQUE 或 PRIMARY KEY)冲突时才执行更新逻辑,不是所有重复都管——比如普通字段重复、非索引字段重复,它完全不响应。
常见误判场景:INSERT INTO user (name) VALUES ('Alice'),但 name 没建唯一索引,即使表里已有同名记录,也不会触发 ON DUPLICATE KEY UPDATE,而是直接报错(如果没设 IGNORE)或成功插入重复行。
- 必须确保冲突字段上有
UNIQUE或PRIMARY KEY索引 - 复合唯一索引也支持,例如
UNIQUE KEY idx_email_status (email, status) - 如果主键是自增
id,但你没在INSERT中指定它,那冲突只能靠其他唯一键触发
UPDATE 子句里怎么引用新值和旧值
ON DUPLICATE KEY UPDATE 后面的赋值表达式中,可以用 VALUES(col_name) 显式取本次 INSERT 尝试写入的值;也可以直接写列名(表示当前已存在的旧值),或用函数组合。
例如:
INSERT INTO user (email, login_count, last_login)
VALUES ('bob@example.com', 1, NOW())
ON DUPLICATE KEY UPDATE
login_count = login_count + 1,
last_login = VALUES(last_login);
-
login_count = login_count + 1:对已有记录的字段做增量更新 -
last_login = VALUES(last_login):用本次 INSERT 的新值覆盖旧值 - 不能写
last_login = NOW()—— 这会两次调用NOW(),且语义模糊(到底是插入时的时间,还是更新触发时的时间?) - 如果想用当前时间,建议统一用
VALUES(last_login)并在VALUES里传NOW()
与 REPLACE INTO 的关键区别在哪
REPLACE INTO 不是“更新”,而是“删+插”:先按唯一键定位并删除旧行,再插入新行。这会导致自增 ID 跳变、外键级联行为被触发、触发器执行两次(DELETE + INSERT),还可能丢失未在 VALUES 中显式指定的字段值。
-
ON DUPLICATE KEY UPDATE是原地更新,ID 不变,不触发 DELETE 相关逻辑 - 若表有
ON DELETE CASCADE外键,REPLACE可能意外级联删数据;ON DUPLICATE KEY UPDATE完全避开这个问题 - 当需要保留未在
INSERT中列出的字段值(如默认值、历史计算字段),必须用ON DUPLICATE KEY UPDATE,不能用REPLACE
批量插入时 ON DUPLICATE KEY UPDATE 的行为边界
它对每行独立判断是否冲突,然后分别决定插入或更新,但整个语句仍是原子的(要么全成功,要么因严重错误回滚)。不过要注意 MySQL 的“严格模式”影响:
- 如果某行插入违反了非唯一约束(如
NOT NULL、类型越界),整条INSERT ... ON DUPLICATE KEY语句会失败,哪怕其他行本可成功 - 启用
STRICT_TRANS_TABLES时,这种失败更常见;关闭后部分错误可能被静默转换(不推荐) - 不要指望它能“跳过坏行继续处理”——真要容错,得拆成单条语句或用
INSERT IGNORE+ 应用层补漏
真正复杂的业务逻辑(比如更新时还要查关联表、做条件判断),别硬塞进 ON DUPLICATE KEY UPDATE,它不是通用脚本引擎——该上存储过程或应用层控制流的时候就得上。


















