必须有唯一约束(主键或唯一索引)才能触发on duplicate key update,否则退化为普通insert;最常见原因是表无primary key或unique key,可用show create table确认;复合唯一索引、大小写敏感排序规则、多唯一索引共存等均影响触发逻辑。

必须有唯一约束(主键或唯一索引)才能触发 ON DUPLICATE KEY UPDATE,否则它就退化成普通 INSERT,不报错也不更新。
为什么插入没触发更新?检查唯一约束是否存在
这是最常踩的坑:表里没建主键,也没加 UNIQUE 索引,ON DUPLICATE KEY UPDATE 完全不会生效。
- 用
SHOW CREATE TABLE table_name;确认输出中包含PRIMARY KEY或UNIQUE KEY - 复合唯一索引也行,比如
UNIQUE KEY idx_user_day (user_id, log_date) - 注意大小写敏感:若字段是
utf8mb4_0900_as_cs排序规则,'A'和'a'被视为不同值,可能意外绕过冲突检测 - 没有唯一约束时,MySQL 直接忽略
ON DUPLICATE KEY UPDATE子句,只执行插入 —— 不报错,但也不如你预期
批量插入时,VALUES() 怎么引用新值
在 ON DUPLICATE KEY UPDATE 子句里,不能直接写变量或字面量来覆盖原值;必须用 VALUES(col_name) 显式引用本次 VALUES 中对应列的值。
- 正确:
INSERT INTO stats (user_id, score) VALUES (123, 50) ON DUPLICATE KEY UPDATE score = VALUES(score); - 错误:
... ON DUPLICATE KEY UPDATE score = 50;—— 这样写虽语法合法,但无法用于批量场景,且易出错 - 支持表达式:
score = score + VALUES(score)、updated_at = NOW() - 多行批量时,每行的
VALUES(col)只绑定当前行,互不干扰
多个唯一索引共存时,更新行为不可控
如果表上有多个唯一索引(比如主键 + 一个 UNIQUE(email) + 一个 UNIQUE(phone)),ON DUPLICATE KEY UPDATE 的匹配逻辑会变得模糊。
- MySQL 只选**第一个匹配到的唯一索引**来定位要更新的行,顺序取决于
SHOW INDEX FROM table_name中的Seq_in_index - 更危险的是:当冲突由非主键唯一索引触发时,
UPDATE实际等价于WHERE col = ? LIMIT 1,可能更新到意料之外的那条记录 - 官方文档明确建议:避免在多唯一索引表上使用该语法;真要这么做,务必确保业务逻辑只依赖单一唯一键
- 自增主键仍会递增:即使最终走的是更新路径,
AUTO_INCREMENT值也会+1,可能导致 ID 间隙变大
影响行数返回值怎么解读
mysql_affected_rows() 或客户端返回的“受影响行数”不是简单的“成功/失败”,而是带语义的:
-
1:新插入一行 -
2:更新了一行(值有变化) -
0:命中了已有行,但所有UPDATE表达式计算结果和原值完全一致(比如name = VALUES(name)且新旧值相同) - 批量时是总和,比如插入 3 行、更新 2 行 → 返回
5 - 注意:如果连接时启用了
CLIENT_FOUND_ROWS标志,0会变成1,这点容易在 Go/Python 驱动里被忽略
真正难的不是写对语法,而是确认你的唯一约束设计是否匹配业务语义——比如用 email 当唯一键,但允许用户改邮箱,那就得额外处理旧邮箱的清理,否则 ON DUPLICATE KEY UPDATE 会不断往旧邮箱上叠数据。











