on duplicate key update 仅在插入触发主键或唯一索引冲突时生效,普通索引、not null 或外键约束不触发;更新仅限子句中明确指定的字段,且 values(col) 引用本次插入值而非原值。

ON DUPLICATE KEY UPDATE 什么时候生效
这个语法只在插入时触碰到主键(PRIMARY KEY)或唯一索引(UNIQUE KEY)冲突才会执行更新逻辑,普通索引、非空约束、外键约束不触发。比如表里有 email 字段加了 UNIQUE 约束,那么插入重复 email 就会进 ON DUPLICATE KEY UPDATE 分支;但插入 NULL 到一个 NOT NULL 字段,报的是 ERROR 1048,这个语法完全不接管。
UPDATE 子句里写字段值的几个坑
别以为 SET name = VALUES(name) 就是安全复制,实际行为受 NULL 和表达式影响很大:
-
VALUES(col)是指本次INSERT中该列的原始值,不是当前行旧值 - 如果写
score = score + 1,而原score是NULL,结果仍是NULL—— 得改成score = IFNULL(score, 0) + 1 - 不能在
UPDATE部分用VALUES(id)去引用刚插的 id,MySQL 不允许这种跨上下文引用 - 如果表有多个唯一键(比如
uid主键 +phone唯一索引),只要任意一个命中就触发更新,但只改你SET里列出的字段
批量插入时怎么知道哪几行被更新/跳过了
MySQL 不返回具体冲突行信息,mysql_affected_rows() 或 Python 的 cursor.rowcount 只给总数:
- 新插入:返回 1
- 冲突并更新:返回 2(MySQL 认为“删旧+插新”两步)
- 冲突但没更新字段(比如
SET x = x):可能返回 0
想定位具体哪条数据撞了,得靠应用层预查:SELECT id FROM table WHERE id IN (1,2,3) 先捞出已存在的 ID,再做差集;或者用临时表 + LEFT JOIN 对比。硬上 ON DUPLICATE KEY UPDATE 却不检查,等于把问题藏进黑盒。
和 INSERT IGNORE、REPLACE INTO 的关键区别
三者语义不同,选错可能引发意外副作用:
-
INSERT IGNORE:冲突时静默跳过整行,auto_increment值仍会自增(哪怕没插入成功) -
REPLACE INTO:本质是DELETE + INSERT,会改变auto_increment值、触发ON DELETE外键级联、重置updated_at时间戳 -
ON DUPLICATE KEY UPDATE:不删不插,只改指定字段,事务内原子性更强,也更可控
真正需要“存在则更新部分字段”的场景,ON DUPLICATE KEY UPDATE 是最贴近意图的解法;但如果业务要求“必须严格保持插入顺序”或“要精确统计每条数据状态”,就得放弃单条 SQL,拆成 SELECT + INSERT/UPDATE 显式控制。











