ON DUPLICATE KEY UPDATE 仅在插入时触发主键或唯一索引冲突才执行 UPDATE,不查是否存在而是先硬插,失败后才更新;要求表必须有 PRIMARY KEY 或 UNIQUE 约束,普通索引无效;UPDATE 中不可用 WHERE,但可用 VALUES(col) 引用插入值;影响行数返回 1(新插入)、2(更新且值变)、0(更新但值未变);AUTO_INCREMENT 仍会递增,高并发下有死锁风险。

直接回答:它只在插入时触发主键或任意唯一索引冲突才执行 UPDATE,不是“存在就更新”的万能语法,也不是 REPLACE INTO 的替代品。
ON DUPLICATE KEY UPDATE 触发条件必须是唯一键冲突
很多人误以为只要记录已存在就会触发更新,其实完全相反:它根本不会去查“是否存在”,而是硬插,插失败了(且失败原因是主键或某个 UNIQUE 索引重复)才切到 UPDATE 分支。
- 表里没建任何
PRIMARY KEY或UNIQUE约束 → 该语法完全不生效,直接报错 - 冲突字段是普通索引(
INDEX)或没加索引 → 不触发,照样报Duplicate entry错误 - 复合唯一索引(如
UNIQUE KEY (a, b))中只要(a,b)组合值已存在 → 就触发 - 多个唯一索引同时冲突(比如主键和邮箱唯一索引都撞了)→ 仍只更新一行,MySQL 内部按索引顺序选一个匹配行,不保证是哪条
UPDATE 部分不能写 WHERE,但可引用插入值用 VALUES(col)
你不能在 ON DUPLICATE KEY UPDATE 后面加 WHERE 条件,所有更新逻辑必须写在赋值表达式里。这时候 VALUES(col) 就很关键——它代表“原本想插入的 col 值”,不是当前行的值。
- 累加场景:
score = score + VALUES(score)→ 存在则加,不存在则插入原始值 - 避免重复写值:
INSERT INTO users (id, name) VALUES (123, 'Alice') ON DUPLICATE KEY UPDATE name = VALUES(name) -
VALUES()在批量插入时特别有用,比如一次插 100 行,UPDATE 部分自动对应每行的原始值 - MySQL 8.0.19+ 支持行别名(
AS row),可写成name = row.name,语义更清晰,但兼容性不如VALUES()
影响行数(affected rows)返回值有三种含义
执行后看 mysql_affected_rows() 或客户端返回的行数,不能只当“成功/失败”看,它携带明确语义:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
-
1:新插入一行(无冲突) -
2:冲突触发 UPDATE,且至少一个字段值被实际修改 -
0:冲突触发 UPDATE,但所有赋值表达式结果和原值完全一样(例如status = VALUES(status),而原值本来就是那个值)
注意:如果连接时设置了 CLIENT_FOUND_ROWS 标志,0 会变成 1,这是 MySQL C API 层的行为,ORM(如 MyBatis、SQLAlchemy)默认不设这个标志,所以多数情况下你看到 0 就是真的“没变”。
容易被忽略的坑:AUTO_INCREMENT 和死锁风险
即使最终走的是 UPDATE 分支,只要 INSERT 尝试执行过,AUTO_INCREMENT 计数器就已递增——这会导致 ID 不连续,且无法回退。
- 高并发下多个事务同时对同一唯一键执行 IODKU → 可能因间隙锁(gap lock)争抢触发
Deadlock found when trying to get lock - 表上有多个唯一索引(比如主键 + email 唯一 + phone 唯一)→ 冲突判断逻辑变复杂,MySQL 可能选错行更新,文档明确建议“尽量避免”
- UPDATE 部分若又引入新唯一冲突(例如把
email改成另一个已存在的值)→ 直接报错退出,不会二次 fallback
真正要稳,得确认表结构只有一处唯一约束作为业务判据,并压测验证并发表现。

















