postgresql 9.5+ 原生支持 insert on conflict,必须依赖已存在的唯一约束或主键约束,否则报错;支持单列/多列冲突目标及显式约束名指定,excluded 伪表用于引用待插入值,分区表和列存表存在兼容性限制。

PostgreSQL 9.5+ 原生支持 INSERT ON CONFLICT,无需先查再插/再更新,一条语句就能完成“存在则更新、不存在则插入”的逻辑。
ON CONFLICT 必须依赖唯一性约束
它不是靠“主键字段名”自动识别冲突,而是靠表上已存在的 UNIQUE 约束或 PRIMARY KEY 约束。没有这些约束,ON CONFLICT 直接报错:there is no unique or exclusion constraint matching the ON CONFLICT specification。
- 建表时没加
PRIMARY KEY或UNIQUE (col),就不能用ON CONFLICT (col) - 多列唯一索引(如
UNIQUE (user_id, event_type))也能作为conflict_target,此时必须写成ON CONFLICT (user_id, event_type) - 如果表有多个唯一约束,又想只针对某一个生效,推荐显式写
ON CONFLICT ON CONSTRAINT constraint_name,避免 PostgreSQL 推断错误
DO UPDATE SET 中的 EXCLUDED 是关键
EXCLUDED 是个伪表,代表本次 INSERT 想插入但因冲突而被“拦下”的那行数据。你在 SET 子句里所有引用新值的地方,都得从它取。
- 正确:
SET updated_at = NOW(), count = EXCLUDED.count + 1 - 错误:
SET count = count + 1—— 这里的count指的是原记录的值,不是新值;加 1 就变成自增两次 - 更新部分字段时,没出现在
SET里的字段保持原值,不会被设为NULL或默认值 - 若字段有
DEFAULT,DO UPDATE不会触发它 —— 所以INSERT ... VALUES (1, DEFAULT)和ON CONFLICT ... DO UPDATE SET col = DEFAULT行为不等价
常见踩坑点:存储引擎与分区表限制
不是所有 PostgreSQL 变体都完全支持该语法,尤其在云数据库或列存场景下,限制很实际。
- AnalyticDB for PostgreSQL 需内核 ≥
V6.3.6.1才支持分区表上的ON CONFLICT - 列存表(
AO/AOCS)不支持 —— 因为它们不支持唯一索引,底层就缺了冲突判断的前提 - Beam 存储引擎只支持全列更新(
DO UPDATE SET (a,b,c) = (EXCLUDED.a, EXCLUDED.b, EXCLUDED.c)),不支持部分列更新;如需灵活更新,得先ALTER TABLE ... SET ACCESS METHOD heap - 同一语句中不能对同一个主键值重复出现多次(如
VALUES (1,'a'), (1,'b')),会报duplicate key violates unique constraint—— 这是 SQL 标准限制,不是 bug
最易被忽略的是:冲突判断发生在唯一约束层面,而不是“主键字段是否相等”这种表面逻辑。如果你删了唯一索引又忘了重建,或者用了表达式索引但没在 ON CONFLICT 中显式指定约束名,语句就会突然失效且报错信息并不直观。










