on duplicate key update 仅在主键或唯一索引冲突时触发更新,不查存在性;无唯一约束则报错;update中不可用where,但可用values()引用插入值;affected_rows返回1(插入)、2(更新且值变)、0(更新但值未变)。

它只在主键或任意唯一索引冲突时才触发 UPDATE,不是“查存在再更新”,更不是万能 insertOrUpdate 替代品;没建 UNIQUE 或 PRIMARY KEY 就直接报错。
ON DUPLICATE KEY UPDATE 触发条件必须是唯一键冲突
很多人以为“只要记录已存在就更新”,其实 MySQL 根本不查是否存在——它先硬插,插失败了(且失败原因是主键或某个 UNIQUE 索引重复)才切到 UPDATE 分支。
- 表里没建任何
PRIMARY KEY或UNIQUE约束 → 该语法完全不生效,直接报Duplicate entry错误 - 冲突字段是普通
INDEX(非唯一)或没加索引 → 不触发,照样报错 - 复合唯一索引(如
UNIQUE KEY (a, b))中只要(a,b)组合值已存在 → 就触发 - 多个唯一索引同时冲突(比如主键和邮箱唯一索引都撞了)→ 仍只更新一行,MySQL 内部按索引顺序选一个匹配行,不保证是哪条
UPDATE 部分不能用 WHERE,但可用 VALUES() 引用插入值
你不能在 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) - MySQL 8.0.19+ 支持行别名(
AS row),可写成name = row.name,语义更清晰,但兼容旧版本建议坚持用VALUES()
affected_rows 返回值有明确语义,别当成布尔值看
执行后看 mysql_affected_rows() 或客户端返回的行数,不能只当“成功/失败”看:
-
1:新插入一行(无冲突) -
2:冲突触发 UPDATE,且至少一个字段值被实际修改 -
0:冲突触发 UPDATE,但所有赋值表达式结果和原值完全一样(例如status = VALUES(status),而原值本来就是那个值) - 注意:
CLIENT_FOUND_ROWS标志会影响0的返回(ORM 如 MyBatis、SQLAlchemy 默认不设,所以多数情况下看到0就是真的“没变”)
批量插入时 VALUES() 是核心,别手写重复值
批量操作时,VALUES() 自动绑定每一行的原始值,这是高效的关键。手写固定值会丢失批量语义,还容易出错。
- 正确写法:
INSERT INTO products (id, price, stock) VALUES (1,19.99,100),(2,29.99,50) ON DUPLICATE KEY UPDATE price = VALUES(price), stock = VALUES(stock) - 条件更新:
price = IF(VALUES(price) - 增量更新:
stock = stock + VALUES(stock_change)(配合INSERT ... VALUES (1,10),(2,-5)) - AUTO_INCREMENT 仍会递增,高并发下有死锁风险——这点常被忽略,尤其在写入密集场景











