插入或更新(UPSERT)是指“如果不存在就插入,否则更新”的操作。PostgreSQL 将其写作 INSERT ... ON CONFLICT ... DO UPDATE(或 DO NOTHING)。它具有原子性、不会产生竞态,也是生产代码中最常用的模式之一。在这个子句出现之前,应用程序必须采用 SELECT-then-INSERT-then-UPDATE 的绕行方式,在并发场景下要么很慢,要么结果错误。
本章要点
使用 INSERT ... ON CONFLICT DO NOTHING 跳过重复项
使用 INSERT ... ON CONFLICT (col) DO UPDATE SET ... 合并新值与旧值
通过 EXCLUDED 虚拟表引用待插入的值
冲突目标:列、约束名称或表达式
RETURNING 与 ON CONFLICT 的配合,以及何时能返回该行
示例
示例 25.1UPSERT(ON CONFLICT)
-- Setup a table with a unique key
DROPTABLEIFEXISTSvisitors_example;CREATETABLEvisitors_example(emailtextPRIMARYKEY,nametextNOTNULL,visitsintegerNOTNULLDEFAULT1,last_seentimestamptzNOTNULLDEFAULTcurrent_timestamp);INSERTINTOvisitors_example(email,name)VALUES('alice@example.com','Alice');-- DO NOTHING: insert if absent, otherwise skip silently
INSERTINTOvisitors_example(email,name)VALUES('alice@example.com','Alice2')ONCONFLICT(email)DONOTHING;-- DO UPDATE: on conflict, merge new values into the existing row
INSERTINTOvisitors_example(email,name)VALUES('alice@example.com','Alice')ONCONFLICT(email)DOUPDATESETvisits=visitors_example.visits+1,last_seen=current_timestamp;-- EXCLUDED refers to the row that was *proposed* by the INSERT
INSERTINTOvisitors_example(email,name)VALUES('bob@example.com','Bob')ONCONFLICT(email)DOUPDATESETname=EXCLUDED.name,-- use the new name
visits=visitors_example.visits+1RETURNING*;-- Conflict on a partial / expression unique index works too
-- CREATE UNIQUE INDEX ON visitors_example (lower(email));
-- INSERT ... ON CONFLICT ((lower(email))) DO UPDATE ...
SELECT*FROMvisitors_exampleORDERBYemail;
冲突目标——ON CONFLICT 后的带括号子句——告诉 PostgreSQL 哪个唯一约束或索引定义了冲突。(email) 与 email 列上的唯一键匹配。你也可以指定约束名称:ON CONFLICT ON CONSTRAINT visitors_example_pkey DO UPDATE ...。对于表达式索引,重复写出该表达式:ON CONFLICT ((lower(email)))。部分索引还可能要求在冲突目标之后提供匹配的谓词。