必须先有唯一约束,否则on conflict直接报错;postgresql只认显式定义的unique或primary key约束,不认业务逻辑、普通索引或not null;需用\d确认约束存在,缺失则用alter table add constraint或create unique index补全。

必须先有唯一约束,否则 ON CONFLICT 直接报错,不是语法问题,是执行前提。
为什么 ON CONFLICT (id) 报 “no unique or exclusion constraint”?
PostgreSQL 不认“业务上应该唯一”,只认已显式创建的 UNIQUE 或 PRIMARY KEY 约束。普通索引、NOT NULL、字段组合的业务逻辑都不算数。
- 用
\d table_name在 psql 里确认目标列旁标着UNIQUE或Pk - 缺失就补:
ALTER TABLE users ADD CONSTRAINT users_id_key UNIQUE (id); - 复合场景(如
(user_id, event_type))必须建对应复合唯一约束:CREATE UNIQUE INDEX ON user_events (user_id, event_type);—— 两个单列唯一索引不等价
ON CONFLICT 的三种写法怎么选?
写法影响可维护性和并发安全性,不是随便挑一个就行。
-
ON CONFLICT (email):仅当该列有默认命名的唯一约束(如users_email_key),且表里只有一个相关约束时可用 -
ON CONFLICT (user_id, event_type):要求该组合字段存在显式定义的复合唯一约束,字段顺序必须完全一致 -
ON CONFLICT ON CONSTRAINT users_email_key:最推荐,尤其表里有多个唯一约束时,避免歧义和误匹配
DO UPDATE SET 里怎么安全引用新值?
别写 name = 'hardcoded' 或 name = VALUES(name) —— 那是 MySQL 写法,PostgreSQL 不认。
- 必须用
EXCLUDED:SET name = EXCLUDED.name, updated_at = NOW() -
WHERE是在老行上判断,不是在EXCLUDED上,例如:WHERE status = 'active'检查的是原记录状态 - 如果
WHERE不成立,整个DO UPDATE被跳过,语句仍返回成功(INSERT 0 1),容易静默失效
最容易被忽略的是:约束必须存在且严格匹配字段顺序与定义,EXCLUDED 必须用对,WHERE 的作用对象是老行——这三点错一个,ON CONFLICT 就从“省事”变成“埋雷”。










