MySQL的INSERT ... ON DUPLICATE KEY UPDATE依赖主键或唯一索引判重,若缺失对应约束则报错或不生效;PG需改用ON CONFLICT语法,且高并发下易死锁。

MySQL里INSERT ... ON DUPLICATE KEY UPDATE报错说“冲突”,其实是主键或唯一索引没建对
这个错误不是语法问题,而是数据库没找到能触发“重复时更新”的约束。MySQL必须靠PRIMARY KEY或UNIQUE索引来判断“哪一行算重复”——如果表里只有普通索引、或者根本没建唯一约束,ON DUPLICATE KEY UPDATE就完全不生效,还可能报1062 Duplicate entry以外的奇怪错误。
- 检查表结构:
SHOW CREATE TABLE table_name;,确认至少有一个UNIQUE或PRIMARY KEY覆盖了INSERT中用来判重的字段(比如email或user_id) - 常见踩坑:只给
id设了主键,但INSERT语句里没提供id,而是用username判重——这时必须给username加UNIQUE索引 - 注意复合唯一索引:如果用
(org_id, code)联合判重,INSERT里这两个字段都得出现,且UNIQUE (org_id, code)必须存在
用INSERT ... ON DUPLICATE KEY UPDATE更新时,VALUES()函数容易写错
VALUES(col)不是取当前行值,而是取本次INSERT语句里对应列的**原始输入值**。很多人误以为它像触发器里的NEW.col,结果更新成了空值或默认值。
- 正确写法:
INSERT INTO users (id, name, status) VALUES (123, 'Alice', 'active') ON DUPLICATE KEY UPDATE name = VALUES(name), status = VALUES(status); - 错误写法:
... ON DUPLICATE KEY UPDATE name = name——这会把原值赋给自己,等于没更新 - 不能跨列引用:
VALUES(non_existent_col)会直接报错,VALUES()只认INSERT子句里明确列出的列名
PostgreSQL不支持ON DUPLICATE KEY UPDATE,别硬套MySQL写法
PostgreSQL用的是INSERT ... ON CONFLICT,语法和语义都不同。直接把MySQL语句扔进PG会报syntax error at or near "ON"。
- 基本等价写法:
INSERT INTO users (id, name) VALUES (123, 'Alice') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name; -
EXCLUDED是PG的关键字,代表本次想插入但被冲突挡住的那行数据,类似MySQL的VALUES(),但更灵活(比如可写EXCLUDED.name || ' (updated)') - 冲突目标必须明确:
ON CONFLICT (email)要求email有UNIQUE索引;也可以写ON CONFLICT ON CONSTRAINT users_email_key指定具体约束名
高并发下ON DUPLICATE KEY UPDATE可能引发死锁,尤其批量操作时
MySQL会对冲突涉及的索引记录加锁,如果多个事务按不同顺序尝试插入/更新同一组主键,就可能互相等待。现象是Deadlock found when trying to get lock。
- 批量处理时,务必按主键/唯一键升序排序后再执行
INSERT,减少锁顺序不一致 - 避免在事务里混用
SELECT ... FOR UPDATE和INSERT ... ON DUPLICATE KEY UPDATE,两者锁机制不同,容易叠加死锁 - 如果只是想“有则跳过”,用
INSERT IGNORE开销更小;如果必须更新,且并发极高,考虑先SELECT再INSERT/UPDATE分两步(但要处理好竞态)










