on duplicate key update未生效的最常见原因是表缺少主键或唯一索引,因该机制仅在违反primary key或unique约束时触发;需确保冲突字段已建对应唯一性约束。

必须有主键或唯一索引,否则 ON DUPLICATE KEY UPDATE 完全不触发。
为什么 ON DUPLICATE KEY UPDATE 什么都没更新?
最常见原因是表缺少主键(PRIMARY KEY)或任何唯一索引(UNIQUE KEY)。MySQL 只在插入时检测到「主键冲突」或「任意一个唯一索引冲突」才会跳转执行 UPDATE 子句。如果表只有普通索引或没索引,语句会直接报错 Duplicate entry ... for key 'PRIMARY' 或干脆当作普通 INSERT 处理(不报错但也不更新)。
检查方式:SHOW CREATE TABLE table_name; 确认输出里存在 PRIMARY KEY 或 UNIQUE KEY 定义。
- 若用的是联合唯一索引(如
UNIQUE KEY (a, b)),则只有当VALUES中a和b同时与某行完全相等时才触发更新 - 单字段唯一索引(如
email VARCHAR(255) UNIQUE)只要VALUES(email)重复就触发,不管其他字段 - 自增主键本身不参与冲突判断——除非你显式在
VALUES中指定该值并撞上已有记录
VALUES(col) 和直接写值有什么区别?
VALUES(col) 是 MySQL 特有的占位符,代表「本次 INSERT 语句中为 col 指定的那个值」,不是函数调用,也不是当前时间戳生成器。它解决的是值复用和语义明确问题。
比如这句:INSERT INTO users (id, name, updated_at) VALUES (1, 'Alice', NOW()) ON DUPLICATE KEY UPDATE name = VALUES(name), updated_at = VALUES(updated_at);
-
VALUES(name)→ 就是字符串'Alice',不会重新查表或计算 -
VALUES(updated_at)→ 就是本次执行时的NOW()结果,只算一次 - 如果写成
updated_at = NOW(),MySQL 会在UPDATE阶段再算一次NOW(),可能和插入时刻差几毫秒,且语义模糊 - 旧版本(VALUES(col),但部分 JDBC 驱动需设
allowMultiQueries=true才能正确解析
批量 INSERT 时,冲突行为怎么算?
每行独立判断:对 INSERT INTO t(a,b,c) VALUES (1,2,3), (1,4,5), (2,6,7) ON DUPLICATE KEY UPDATE c = VALUES(c);
- 假设
a是主键,第一行(1,2,3)插入成功 → 影响行数 +1 - 第二行
(1,4,5)因a=1冲突 → 更新已存在行的c为5→ 影响行数 +2(MySQL 认为“尝试插入+实际更新”共两步) - 第三行
(2,6,7)无冲突 → 插入 → 影响行数 +1 - 最终
affected_rows = 4(1+2+1),不是 3 - 注意:即使某行同时违反主键和唯一索引(比如
a主键和b唯一都撞了),也只触发一次UPDATE,不会重复更新
和 REPLACE INTO 的关键区别在哪?
REPLACE INTO 是「删+插」:先按唯一键定位并删除旧行,再插入新行;而 ON DUPLICATE KEY UPDATE 是原地修改,不动主键值、不触发 DELETE 相关逻辑。
-
REPLACE会导致自增 ID 跳变(删掉再插,ID 加 1);ODKU不改变 ID - 如果表有
ON DELETE CASCADE外键,REPLACE可能意外级联删子表数据;ODKU完全避开 -
REPLACE会丢失未在VALUES中显式列出的字段值(比如默认值、计算列);ODKU只改你指定的字段,其余保持不变 -
REPLACE触发器会执行DELETE和INSERT各一次;ODKU只触发INSERT触发器(或都不触发,取决于 MySQL 版本和配置)
真正容易被忽略的是:当你要保留历史字段(比如 created_at 时间戳)又只更新部分字段时,ODKU 是唯一安全选择;REPLACE 会强制重置所有未指定字段为默认值或 NULL。











