它只在插入时触发主键或任意唯一索引冲突才执行UPDATE,不是“存在就更新”的万能语法;要求表必须有PRIMARY KEY或UNIQUE约束,普通索引无效,且UPDATE中不可用WHERE但可用VALUES(col)引用插入值。

ON DUPLICATE KEY UPDATE 什么情况下才生效
它只在插入时触发唯一约束(UNIQUE 或 PRIMARY KEY)冲突时才执行更新逻辑,不是“查一遍再决定更新”,更不是“存在就更新”的通用语义。如果你的表没建唯一索引,哪怕数据完全重复,INSERT ... ON DUPLICATE KEY UPDATE 也只会报错或静默忽略(取决于 SQL mode),根本不会走到更新分支。
常见错误:只加了普通索引、或者忘了给业务字段(比如 email、order_no)建 UNIQUE 约束,结果语句看似执行成功,但重复数据照常插入,更新逻辑形同虚设。
- 必须确保冲突字段上有
UNIQUE或PRIMARY KEY约束 - 复合唯一索引也支持,比如
UNIQUE KEY (user_id, item_id) - 注意 NULL 值不参与唯一性判断 —— 两个
NULL不算冲突,这点容易被忽略
UPDATE 子句里怎么引用刚想插入的值
不能直接写变量名或列名,得用 VALUES(col_name) 函数。这是最常写错的地方:很多人写成 SET name = name 或 SET name = 'xxx',结果更新成了旧值或固定字符串,而不是本次 INSERT 想塞进去的那个值。
示例:向用户表插入新记录,邮箱冲突则更新昵称和更新时间:
INSERT INTO users (email, nickname, created_at)
VALUES ('a@b.com', 'Alice', NOW())
ON DUPLICATE KEY UPDATE
nickname = VALUES(nickname),
updated_at = NOW();
-
VALUES(nickname)指的是本次INSERT语句中对应位置的值,不是当前行的旧值 - 可以混用,比如
status = IF(VALUES(score) > score, 'top', status) - 不支持子查询,也不能用
LAST_INSERT_ID()这类会话级函数
影响行数返回值怎么看懂
MySQL 返回的“受影响行数”有三种可能:1(正常插入)、2(冲突后更新)、0(冲突但更新值和原值完全相同)。别看到是 2 就以为一定改了数据 —— 实际上如果 UPDATE 后字段值没变,MySQL 仍计为 2 行(1 行插入尝试 + 1 行更新动作),但磁盘和 binlog 都没写。
- 用
mysql_affected_rows()或mysqli->affected_rows获取该值 - 若需区分“真更新”和“假更新”,得在应用层比对新旧值,或加
updated_at = NOW()强制变化 - 开启
sql_mode=STRICT_TRANS_TABLES时,若更新导致数据截断,整个语句会失败,不会退化为插入
和 REPLACE INTO 的关键区别在哪
REPLACE INTO 是“删+插”,会触发 DELETE 和 INSERT 两个事件,外键级联、触发器、自增 ID 都受影响;而 ON DUPLICATE KEY UPDATE 是纯更新,不删除原行,自增 ID 不变,也不会触发 DELETE 相关逻辑。
- 有外键引用时,
REPLACE可能因级联删除引发意外数据丢失 - 主键是自增字段时,
REPLACE会让 ID 跳变,ON DUPLICATE KEY UPDATE则保持稳定 -
REPLACE在无主键/唯一索引时会变成普通插入,而ON DUPLICATE KEY UPDATE会直接报错
真正需要“存在即更新”时,几乎总是该选 ON DUPLICATE KEY UPDATE,除非你明确需要重建整行并接受 ID 变动和触发器重放。


















