PostgreSQL 19: 支持 INSERT ON CONFLICT DO SELECT

John Doe 三月 5, 2026

PostgreSQL 19 支持了 INSERT ON CONFLICT DO SELECT 语法,原子化解决了插入冲突的返回难题。

image

特性提交日志

新增对 INSERT … ON CONFLICT DO SELECT 的支持。

本次提交新增了一种 ON CONFLICT 动作:DO SELECT [FOR UPDATE/SHARE],用于在检测到冲突时返回表中已存在的行。当指定 DO SELECT 时,INSERT 语句必须包含 RETURNING 子句。

可选的 FOR UPDATE/SHARE 子句允许在返回行之前对其加锁。与 DO UPDATE 冲突处理动作类似,可以使用可选的 WHERE 子句来过滤掉不需要返回的行(但与DO UPDATE行为一致:被WHERE子句过滤掉的行仍然会被加锁)。

讨论:https://postgr.es/m/d631b406-13b7-433e-8c0b-c6040c4b4663@Spark

示例

INSERT ... ON CONFLICT的核心价值,是解决了传统 “先查后插” 逻辑的竞态问题,提供了两种冲突处理动作:

  • DO NOTHING:冲突时放弃插入,不报错,保证语句执行成功;
  • DO UPDATE:冲突时更新已存在的行,实现 “存在则更新,不存在则插入” 的原子逻辑。

本次提交新增了ON CONFLICT ... DO SELECT动作:当插入检测到唯一约束冲突时,不执行插入、不修改任何数据,直接返回已存在的冲突行,同时完整继承了ON CONFLICT的原子性与并发安全性。

简单说,这个新子句的适用场景与 INSERT ... RETURNING 完全一致:只要表中包含由触发器生成或填充的列。或者你使用的数据类型会对插入的数据进行四舍五入 / 截断处理,该子句就能发挥作用。总的来说,INSERT ... RETURNING 帮你省去了额外查询刚插入行的麻烦,无需手动查询即可知道数据库实际存储在表中的数据内容。

举一个简单的例子,我们创建一张存储人员信息的表,包含自动生成的主键、社保号(唯一)和姓名字段:

CREATE TABLE person (
   id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
   svnr varchar(10) UNIQUE NOT NULL,
   name text NOT NULL
);

如果你想知道插入行时序列生成的 id 值,可以使用以下语句:

INSERT INTO person (svnr, name) VALUES
   ('1750201068', 'Mike')
RETURNING id;

如果要插入的数据可能已存在于表中,你可以通过以下方式忽略重复行:

INSERT INTO person (svnr, name) VALUES
   ('1750201068', 'Mike'),
   ('1053080982', 'mary')
ON CONFLICT (svnr) DO NOTHING;

你也可以将 ON CONFLICT DO NOTHINGRETURNING 结合使用,但此时语句只会返回新插入行的 id。如果需要获取那些尝试插入但已存在于表中的行的 id,你要么额外执行一条 SELECT 语句,要么使用新增的 ON CONFLICT ... DO SELECT 语法:

INSERT INTO person (svnr, name) VALUES
   ('1750201068', 'Mike'),
   ('1053080982', 'mary')
ON CONFLICT (svnr) DO SELECT
RETURNING id;

非常不错的体验,感谢所有参与的社区人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/88327092ff06c48676d2a603420089bf493770f3