news 2026/6/10 16:11:56

PostgreSQL 18 RETURNING 增强:现代应用的重要进展

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PostgreSQL 18 RETURNING 增强:现代应用的重要进展

PostgreSQL 18 正式发布,带来了多项重要改进,其中 RETURNING 子句的增强尤为突出。该特性在 MERGE RETURNING 场景下实现了关键突破,可显著简化应用架构,并提升数据变更追踪能力。

RETURNING 子句的演进

RETURNING 子句长期以来用于在INSERTUPDATEDELETE操作后返回受影响行的数据,从而避免额外的 SELECT 查询,减少数据库往返次数并提升性能。然而,在 PostgreSQL 18 之前,该子句在功能上存在明显限制,迫使开发实践中采用各种折中方案。

在 PostgreSQL 17 中,首次为MERGE语句引入 RETURNING 支持(提交c649fa24a),这是一次重要进展。MERGE语句自 PostgreSQL 15 引入,用于在单条语句中完成条件化的INSERTUPDATEDELETE操作,但在缺乏 RETURNING 支持的情况下,无法直观获取实际执行结果。

PostgreSQL 18 的新特性

PostgreSQL 18 通过引入 OLD 与 NEW 别名(提交 80feb727c8,由 Dean Rasheed 提交,Jian He 与 Jeff Davis 评审),将 RETURNING 子句能力提升至新的层级。该增强使 DML 操作期间的数据捕获方式发生了根本性变化。

PostgreSQL 18 之前的限制

在早期版本中,RETURNING 子句在不同语句类型下存在以下差异化限制:

  • INSERTUPDATE仅能返回新值或当前值
  • DELETE仅能返回旧值
  • MERGE根据内部实际执行的操作类型(INSERTUPDATEDELETE)返回结果

在需要对比更新前后数据、或精确追踪字段变化时,可选方案较为有限,包括:

  • 在修改前额外执行SELECT查询
  • 编写复杂的触发器函数
  • 在应用层实现变更跟踪逻辑
  • 通过系统列(如 xmax)进行间接判断

上述方式普遍增加了实现复杂度与访问延迟,并降低了代码可维护性。

解决方案:OLD 与 NEW 别名

PostgreSQL 18 引入了特殊别名oldnew,可在单条语句中同时访问数据的修改前状态与修改后状态。该机制适用于INSERTUPDATEDELETE以及MERGE等全部 DML 操作。

基本语法示例如下:

UPDATE table_name SET column = new_value WHERE condition RETURNING old.column AS old_value, new.column AS new_value;

为避免与现有列名冲突,或在触发器环境中使用,可对别名进行重命名:

UPDATE accounts SET balance = balance - 50 WHERE account_id = 123 RETURNING WITH (OLD AS previous, NEW AS current) previous.balance AS old_balance, current.balance AS new_balance;

MERGE + RETURNING:能力整合

在 PostgreSQL 18 中,MERGE 与 RETURNING 的组合为 Upsert 场景提供了完整能力,可在单条原子操作中同时完成数据写入与变更结果获取。

实践示例:产品库存系统

在产品库存管理场景中,需要从外部数据源同步数据,实现新增产品、更新已有产品,并准确记录每一行的处理结果。

步骤 1:创建数据表

CREATE TABLE products ( product_id SERIAL PRIMARY KEY, product_code VARCHAR(50) UNIQUE NOT NULL, product_name VARCHAR(200) NOT NULL, price DECIMAL(10, 2) NOT NULL, stock_quantity INTEGER NOT NULL DEFAULT 0, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE product_staging ( product_code VARCHAR(50), product_name VARCHAR(200), price DECIMAL(10, 2), stock_quantity INTEGER );

步骤 2:插入初始数据

INSERT INTO products (product_code, product_name, price, stock_quantity) VALUES ('LAPTOP-001', 'Premium Laptop', 999.99, 50), ('MOUSE-001', 'Wireless Mouse', 29.99, 200), ('KEYBOARD-001', 'Mechanical Keyboard', 79.99, 150); INSERT INTO product_staging (product_code, product_name, price, stock_quantity) VALUES ('LAPTOP-001', 'Premium Laptop Pro', 1099.99, 45), -- Update existing ('MONITOR-001', '4K Monitor', 399.99, 75), -- New product ('MOUSE-001', 'Wireless Mouse', 29.99, 200); -- No actual change

基础版:搭配 RETURNING 子句的 MERGE 操作

MERGE INTO products p USING product_staging s ON p.product_code = s.product_code WHEN MATCHED THEN UPDATE SET product_name = s.product_name, price = s.price, stock_quantity = s.stock_quantity, last_updated = CURRENT_TIMESTAMP WHEN NOT MATCHED THEN INSERT (product_code, product_name, price, stock_quantity) VALUES (s.product_code, s.product_name, s.price, s.stock_quantity) RETURNING p.product_code, p.product_name, merge_action() AS action_performed;

返回结果示例:

product_code | product_name | action_performed ---------------+---------------------+------------------ LAPTOP-001 | Premium Laptop Pro | UPDATE MONITOR-001 | 4K Monitor | INSERT MOUSE-001 | Wireless Mouse | UPDATE

进阶版:搭配 OLD 与 NEW 别名的 MERGE 操作

通过 OLD 与 NEW 别名,可同时获取字段的修改前与修改后值,从而实现精细化变更追踪与审计。

以下查询可从受影响行中,同时获取 product_name 与 price 列的修改前旧值和修改后新值。通过为其设置别名(old_name、new_name、old_price、new_price),可便捷对比 MERGE 操作前后的列值变化,为变更追踪与审计日志记录提供支撑。

MERGE INTO products p USING product_staging s ON p.product_code = s.product_code WHEN MATCHED THEN UPDATE SET product_name = s.product_name, price = s.price, stock_quantity = s.stock_quantity, last_updated = CURRENT_TIMESTAMP WHEN NOT MATCHED THEN INSERT (product_code, product_name, price, stock_quantity) VALUES (s.product_code, s.product_name, s.price, s.stock_quantity) RETURNING p.product_code, merge_action() AS action, old.product_name AS old_name, new.product_name AS new_name, old.price AS old_price, new.price AS new_price, old.stock_quantity AS old_stock, new.stock_quantity AS new_stock, (old.price IS DISTINCT FROM new.price) AS price_changed, (old.stock_quantity IS DISTINCT FROM new.stock_quantity) AS stock_changed;

INSERT 场景下旧值为 NULL,而 UPDATE 场景下可完整呈现字段变更情况。

product_code | action | old_name | new_name | old_price | new_price | old_stock | new_stock | price_changed | stock_changed ---------------+--------+-------------------+---------------------+-----------+-----------+-----------+-----------+---------------+-------------- LAPTOP-001 | UPDATE | Premium Laptop | Premium Laptop Pro | 999.99 | 1099.99 | 50 | 45 | t | t MONITOR-001 | INSERT | NULL | 4K Monitor | NULL | 399.99 | NULL | 75 | NULL | NULL MOUSE-001 | UPDATE | Wireless Mouse | Wireless Mouse | 29.99 | 29.99 | 200 | 200 | f | f

构建审计日志

借助增强后的 RETURNING 子句,可在不使用触发器的前提下构建完整审计链路。

步骤 1:创建审计表

CREATE TABLE product_audit ( audit_id SERIAL PRIMARY KEY, product_code VARCHAR(50), action VARCHAR(10), old_values JSONB, new_values JSONB, changes JSONB, changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );

步骤 2:执行带详细审计追踪的 MERGE 操作

WITH merge_results AS ( MERGE INTO products p USING product_staging s ON p.product_code = s.product_code WHEN MATCHED THEN UPDATE SET product_name = s.product_name, price = s.price, stock_quantity = s.stock_quantity, last_updated = CURRENT_TIMESTAMP WHEN NOT MATCHED THEN INSERT (product_code, product_name, price, stock_quantity) VALUES (s.product_code, s.product_name, s.price, s.stock_quantity) RETURNING p.product_code, merge_action() AS action, jsonb_build_object( 'name', old.product_name, 'price', old.price, 'stock', old.stock_quantity ) AS old_values, jsonb_build_object( 'name', new.product_name, 'price', new.price, 'stock', new.stock_quantity ) AS new_values ) INSERT INTO product_audit (product_code, action, old_values, new_values, changes) SELECT product_code, action, old_values, new_values, CASE WHEN action = 'INSERT' THEN new_values WHEN action = 'DELETE' THEN old_values ELSE ( SELECT jsonb_object_agg(key, value) FROM jsonb_each(new_values) WHERE value IS DISTINCT FROM old_values->key ) END AS changes FROM merge_results;

步骤 3:查询审计追踪结果

select * from product_audit; audit_id | product_code | action | old_values | new_values | changes | changed_at ----------+--------------+--------+---------------------------------------------------------------+------------- --------------------------------------------------+---------+---------------------------- 1 | LAPTOP-001 | UPDATE | {"name": "Premium Laptop Pro", "price": 1099.99, "stock": 45} | {"name": "Pr emium Laptop Pro", "price": 1099.99, "stock": 45} | | 2025-12-12 16:27:14.760125 2 | MONITOR-001 | UPDATE | {"name": "4K Monitor", "price": 399.99, "stock": 75} | {"name": "4K Monitor", "price": 399.99, "stock": 75} | | 2025-12-12 16:27:14.760125 3 | MOUSE-001 | UPDATE | {"name": "Wireless Mouse", "price": 29.99, "stock": 200} | {"name": "Wi reless Mouse", "price": 29.99, "stock": 200} | | 2025-12-12 16:27:14.760125 (3 rows)

示例中通过 CTE 获取 MERGE 结果,并将旧值、新值及差异以 JSONB 形式写入审计表,实现单条原子操作内的数据同步与审计记录生成。

未来展望

PostgreSQL 18 版本的 RETURNING 子句增强特性,是该数据库提升开发友好性、减少复杂替代方案使用的重要举措。单原子操作中同时调用数据新旧值的能力,可简化应用开发中的多种通用实现模式。

该功能在后续版本中或可从以下方向进一步升级:

  1. 扩展 MERGE 语句能力,新增更多 WHEN 子句,实现更复杂的条件操作.
  2. 新增聚合功能支持,支持对 RETURNING 子句的返回结果直接进行聚合计算。
  3. 实现跨表返回,支持在单操作中返回关联表的数据信息。

技术细节与提交记录参考

针对关注技术实现细节的人员,可参考以下信息:

  • MERGE RETURNING(PostgreSQL 17):迪恩・拉希德提交,记录编号c649fa24a
  • OLD/NEW Support(PostgreSQL 18):迪恩・拉希德提交,何健与杰夫・戴维斯评审,记录编号80feb727c8
  • Discussion Thread:https://postgr.es/m/CAEZATCWx0J0-v=Qjc6gXzR=KtsdvAE7Ow=D=mu50AgOe+pvisQ@mail.gmail.com

该功能的实现涉及多个组件的修改,包括:

  • 执行器(execExpr.c、execExprInterp.c、nodeModifyTable.c)
  • 解析器(parse_target.c)
  • 优化器(createplan.c、setrefs.c、subselect.c)
  • 节点模块(makefuncs.c、nodeFuncs.c)

总结

PostgreSQL 18 对 RETURNING 子句的增强,尤其是 OLD 与 NEW 别名的引入,为INSERTUPDATEDELETEMERGE操作提供了完整的数据变更可视性。这一能力显著减少了对触发器与额外查询的依赖,使数据同步、变更追踪与审计实现更加简洁、高效且易于维护。

MERGE与增强型 RETURNING 的结合,为 Upsert 场景提供了前所未有的控制能力与透明度,是 PostgreSQL 在开发友好性与工程实用性方面的重要进展。

原文链接:

https://www.pgedge.com/blog/postgresql-18-returning-enhancements-a-game-changer-for-modern-applications

作者:Ahsan Hadi


HOW 2026 议题招募中

2026 年 4 月 27-28 日,由 IvorySQL 社区联合 PGEU(欧洲 PG 社区)、PGAsia(亚洲 PG 社区)共同打造的 HOW 2026(IvorySQL & PostgreSQL 技术峰会) 将再度落地济南。届时,PostgreSQL 联合创始人 Bruce Momjian 等顶级大师将亲临现场。

自开启征集以来,HOW 2026 筹备组已感受到来自全球 PostgreSQL 爱好者的澎湃热情。为了确保大会议题的深度与广度,我们诚邀您在 2026 年 2 月 27 日截止日期前,提交您的技术见解。

投递链接:https://jsj.top/f/uebqBc

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 14:06:07

给你一份 M4 32GB 的本地大模型清单

从“能跑”到“值得长期用”,一次帮你选明白(Qwen / LLaMA / Mistral / DeepSeek)如果你用的是 MacBook Pro M4 32GB,你现在其实站在一个很微妙、也很舒服的位置上:你已经 明显超过“只能玩 7B”这一档但还没到“可以…

作者头像 李华
网站建设 2026/6/9 20:51:25

宏智树 AI:ChatGPT 学术版驱动,重新定义学术写作效率与品质

当学术写作遇上 AI5.0 技术革命,宏智树 AI 以颠覆性姿态重塑学术创作生态!作为由 ChatGPT 学术版模型驱动、搭载 AI5.0 技术架构的专业学术智能解决方案平台,宏智树 AI 打破传统写作工具的局限,为广大学子与科研人员提供从开题到答…

作者头像 李华
网站建设 2026/6/10 15:00:07

计算机毕业设计springboot学科竞赛活动报名系统 基于Spring Boot的学科竞赛活动报名与管理系统设计 Spring Boot框架下的学科竞赛活动在线报名平台开发

计算机毕业设计springboot学科竞赛活动报名系统yzqb4 (配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。 随着信息技术的飞速发展,高校和各类学术机构对学科竞赛活动的…

作者头像 李华
网站建设 2026/6/9 20:10:46

字符串不是字符串——聊聊「字符串的编码与解码(Encode and Decode Strings)」这道被严重低估的算法题

🧵 字符串不是字符串 ——聊聊「字符串的编码与解码(Encode and Decode Strings)」这道被严重低估的算法题 如果你刷过 LeetCode,Encode and Decode Strings 这题,第一眼大概率会觉得: “这不就拼字符串吗?有啥好说的?” 但说句掏心窝子的实话: 这题是字符串处理里…

作者头像 李华