PostgreSQL码农集散地

PostgreSQL upsert 返回已存在记录这个功能有什么用?

本期播客

PostgreSQL upsert 返回已存在记录这个功能有什么用?

在数据库开发的漫长岁月中,有一个经典逻辑像幽灵一样困扰着每一位程序员:“如果这条记录不存在就插入,如果存在就把它查出来给我。”

这个看似简单的需求,在过去几十年的 SQL 标准里,竟然是一个极其昂贵且充满陷阱的操作。然而,2026 年 2 月,PostgreSQL 再次祭出神来之笔:正式支持 INSERT ... ON CONFLICT DO SELECT。

别被这短短的一行 SQL 骗了,这绝对不是什么“语法糖”,这是一场关于原子性与 网络往返(RTT) 的效率革命。

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=88327092ff06c48676d2a603420089bf493770f3

一、 痛点:被逼出来的“多表单病”与 Race Condition

在没有 DO SELECT 之前,我们要实现“不存在则插入,存在则返回”通常有两种选择,但它们都是“坑”:

  1. 先 SELECT,再 INSERT:
  • 风险: 经典的竞态条件(Race Condition)。在并发环境下,两个进程同时发现记录不存在,同时尝试插入,必然导致一个成功,另一个报唯一键冲突。
  1. INSERT ... ON CONFLICT DO UPDATE:
  • 代价: 为了能拿到返回值,即便数据没变,你也不得不触发一次 UPDATE。
  • 后果: 产生大量的 Dead Tuples(死亡元组) ,无端引发 VACUUM 压力,甚至导致索引膨胀。为了读一个值而去写一次盘,这在架构设计上简直是“杀鸡用牛刀”。

二、 技术剖析:原子性的“降维打击”

此次更新支持 ON CONFLICT DO SELECT [FOR UPDATE/SHARE],配合 RETURNING 子句,完美解决了上述矛盾。

1. 彻底消灭网络往返(1 vs N)

从第一性原理来看,数据库性能的瓶颈往往不在 CPU,而在 IO 和网络。

  • 旧逻辑: 1. 尝试插入 -> 2. 捕获异常 -> 3. 重新查询。这至少涉及 2 到 3 次 数据库交互。
  • 新逻辑: 一条 SQL 发过去,数据库内核在检测到冲突的瞬间,直接在内存中转向读取逻辑并返回。性能提升不是百分比级别的,是倍数级别的。

2. 带有“防御性锁定”的读取

这次更新最硬核的是支持 FOR UPDATE。这意味着在冲突发生的瞬间,你不仅拿到了旧数据,还顺手给它加了锁。这对于分布式锁、库存预扣、唯一序列生成等场景,简直是教科书级的原子操作。

三、 权威支撑:为什么它是高并发系统的救星?

1. 权威案例:社交平台的“关注”逻辑

想象一下某社交平台(如微博、X)的“关注”功能:用户连续点击关注,或者多个客户端同时操作。

场景: 插入一条“关注关系”。如果已关注,则返回关注时间。
数据证明: 在超高并发下,由于 ON CONFLICT DO UPDATE 导致的行锁竞争和页面清理(Vacuum),吞吐量往往会遇到瓶颈。改为 DO SELECT 后,写操作变成了纯读操作,行锁持有时间缩短了 60% 以上。

2. ID 生成器与映射表

在处理海量短链接或标签映射(Tagging System)时,通常需要:输入一个字符串,返回其对应的唯一自增 ID。

  • 旧痛: 每天处理 10 亿次映射,如果 90% 是重复请求,那么旧版的 DO UPDATE 会产生 9 亿次无效写入。
  • 新利器:DO SELECT 让这 9 亿次操作变成了纯内存扫描,存储成本和 IOPS 直接下降了一个数量级。

四、 逻辑的崩塌:如果这还不够快?

前提假设: 我们假设“原子性”是所有业务的底线。
条件崩塌: 如果你的业务对数据一致性要求不高,但对响应延迟有极致追求(例如日志打点、不计后果的计数器),那么即便是 DO SELECT 也会因为主键冲突检查而产生开销。

引申观点: 在这种情况下,你需要的可能不是更强的 UPSERT,而是 布隆过滤器(Bloom Filter) 或 缓存预检。但在绝大多数金融、电商、CRM等对数据准确性有硬性要求的领域,ON CONFLICT DO SELECT 就是目前的“最优解”。

结语

PostgreSQL 再次证明了为什么它是“开发者的宠儿”。它敏锐地察觉到了程序员在编写 if-else 逻辑时的痛苦,并将这种痛苦在内核层面予以消解。

INSERT ... ON CONFLICT DO SELECT 的出现,标志着 PG 在处理 幂等性接口(Idempotent API) 时,已经达到了工业界的顶峰。它不仅省下了代码量,更省下了昂贵的服务器 IOPS 和 DBA 的头发。

那么问题来了:既然一条 SQL 就能优雅解决“插不进去就查出来”的问题,你代码里那些臃肿的 try-catch 重试逻辑,是不是该删一删了?

最后附一则济南的线下峰会消息:
PostgreSQL & IvorySQL 2026 年度峰会将于4月份在济南召开,这是目前国内规模最大的PG峰会. 
我是新特性分论坛出品人,欢迎报名参与分享,主委会可解决分享嘉宾住宿和路费。
报名地址:  https://jsj.top/f/uebqBc

议题方向:

  • PostgreSQL 新功能
  • PostgreSQL 内核机制与性能优化
  • PostgreSQL 扩展程序
  • AI + PostgreSQL 技术实践
  • 云原生PostgreSQL或IvorySQL
  • PostgreSQL 用户实践
  • IvorySQL 兼容性与生态实践
  • 基准测试与性能调优
  • 高可用性技术
  • …… 任何与PostgreSQL或IvorySQL相关的内容