必须提前建唯一索引,否则ON DUPLICATE KEY UPDATE仅插入不更新;需确保目标字段有UNIQUE或PRIMARY KEY约束,联合去重须建联合唯一索引,VALUES()仅引用INSERT列表中的字段,影响行数返回值含义特殊,REPLACE INTO语义不同且副作用大。

必须提前建好唯一索引,否则 ON DUPLICATE KEY UPDATE 什么也不会更新,只会默默插入——这是 90% 的人踩坑的第一步。
为什么 ON DUPLICATE KEY UPDATE 总是只插入、从不更新?
根本原因不是 SQL 写错了,而是表结构没配对。MySQL 只在插入时触发唯一约束(PRIMARY KEY 或 UNIQUE)冲突,才会跳进 UPDATE 分支;没冲突,就当普通 INSERT 执行。
- 检查目标字段是否真有
UNIQUE约束:用SHOW CREATE TABLE table_name看输出里有没有UNIQUE KEY行 - 常见疏漏:想按
email去重,但只加了普通索引(INDEX),没加UNIQUE - 联合去重必须建联合唯一索引,不能只对单列加
UNIQUE:ALTER TABLE users ADD UNIQUE (tenant_id, user_id) - 大小写敏感字段(如
COLLATE utf8mb4_0900_as_cs)下,'A'和'a'被视为不同值,不会触发冲突
VALUES() 函数只能引用 INSERT 列表中明确写出的字段
VALUES(col_name) 不是“取当前行任意字段的值”,它只取你 INSERT INTO ... (col1, col2) VALUES (?, ?) 里实际列出的那些列对应的值。漏写 = 报错。
- 错误写法:
INSERT INTO users (email) VALUES ('a@b.com') ON DUPLICATE KEY UPDATE name = VALUES(name)→ 报错Unknown column 'name' in 'field list' - 正确写法:要把
name放进 INSERT 列表:INSERT INTO users (email, name) VALUES ('a@b.com', 'Alice') ON DUPLICATE KEY UPDATE name = VALUES(name) - 想设默认值或函数值(如
NOW()、DEFAULT),不能用VALUES(),得直接写:updated_at = NOW()
影响行数返回值含义容易误读
mysql_affected_rows() 或客户端返回的“受影响行数”不是“变更了多少行”,而是 MySQL 内部状态码的映射:
- 返回
1:纯插入(无冲突) - 返回
2:有冲突,执行了 UPDATE(哪怕新旧值完全一样) - 返回
0:有冲突,但 UPDATE 子句里所有字段的新旧值都相同(MySQL 5.7+ 默认行为) - 注意:如果 UPDATE 子句里写了
col = col + 1这类表达式,即使值变没变,也大概率返回2
别把 REPLACE INTO 当成等价替代
REPLACE INTO 是先删后插,会破坏自增 ID 连续性、触发外键级联删除、丢失触发器中的旧值上下文,和 ON DUPLICATE KEY UPDATE 的原子更新语义完全不同。
- 如果你的表有外键引用,
REPLACE INTO可能导致子表记录被意外删掉 - 自增主键会跳变(比如原 ID=5 的记录被 replace,新记录 ID 变成 12)
- 无法保留原记录中未在 REPLACE 语句中指定的字段值(
ON DUPLICATE KEY UPDATE可以只更新部分字段,其余不变) - 真正需要“覆盖式替换”且能接受副作用时才考虑
REPLACE INTO,日常 upsert 场景优先用ON DUPLICATE KEY UPDATE
最常被忽略的一点:这个机制只响应索引冲突,不响应业务逻辑判断。想按“最近 1 小时内是否已存在”来 upsert?ON DUPLICATE KEY UPDATE 做不到,得换方案。


















