PostgreSQL 18 实战:RETURNING 子句同时拿 OLD 和 NEW
PostgreSQL 18 的 RETURNING 子句支持同时引用 OLD 和 NEW,一条 UPDATE/DELETE 即可拿到修改前后完整行。本文讲清四种 DML 操作下 OLD/NEW 的取值规则,以及计算变更差值、检测真实修改、审计日志等实战写法。
**以前 RETURNING 有个别扭的限制:INSERT/UPDATE 只能返回新值,DELETE 只能返回旧值。**想对比修改前后?得再发一条 SELECT、写触发器,或在应用层拼装。PostgreSQL 18 给 RETURNING 加上了 OLD 和 NEW 别名,一条语句同时拿到前后两个状态,审计日志、变更追踪、条件处理全部简化。
OLD 和 NEW 在各操作里的取值规则
| 操作 | OLD | NEW |
|---|---|---|
| INSERT | 全 NULL(没有旧状态) | 插入的数据 |
| UPDATE | 修改前的值 | 修改后的值(最能发挥的场景) |
| DELETE | 被删除的数据 | 全 NULL(没有新状态) |
| MERGE | 取决于每行实际执行的是 INSERT/UPDATE/DELETE | 同上 |
基础验证:
UPDATE products
SET price = 899.99
WHERE name = 'Laptop'
RETURNING
name,
OLD.price AS old_price,
NEW.price AS new_price;
name | old_price | new_price
---------+-----------+-----------
Laptop | 999.99 | 899.99
写 NEW.price 等价于裸写 price,但显式前缀让意图更清楚,并且能和 OLD 同场出现。
UPDATE 的三个实战模式
1. 直接算变更差值
UPDATE products
SET price = price * 1.10
WHERE price < 100
RETURNING
name,
OLD.price AS original_price,
NEW.price AS updated_price,
NEW.price - OLD.price AS price_increase,
ROUND(((NEW.price - OLD.price) / OLD.price * 100), 2) AS percent_increase;
一次 10% 调价,绝对涨幅和百分比当场算出,RETURNING 本身就是一份完整的变更轨迹。
2. 检测"是否真的改了"
UPDATE 匹配了行不代表值真的变了。加一个布尔判断:
UPDATE products
SET stock_quantity = 150
WHERE name = 'Keyboard'
RETURNING
name,
OLD.stock_quantity,
NEW.stock_quantity,
(OLD.stock_quantity IS DISTINCT FROM NEW.stock_quantity) AS was_modified;
库存本来就是 150 时 was_modified = false。审计日志只记真实变更、应用对"空更新"做不同响应,都靠这个模式。
3. 条件化审计
把 OLD/NEW 的比较逻辑嵌进 RETURNING 表达式,只对敏感字段变化(如价格变动超 5%)输出审计标记,应用层按标记决定是否落审计表——过滤逻辑前置到数据库一次完成,省掉应用端的行级 diff。
INSERT / DELETE / MERGE 里怎么用
- INSERT:
OLD.*全 NULL,价值不大,但 MERGE 场景中统一引用 OLD/NEW 能让单条语句兼容三种分支; - DELETE:
NEW.*全 NULL,OLD.*返回被删行——配合归档场景,DELETE ... RETURNING OLD.*一条语句完成"删除+取回存档数据"; - MERGE:每行实际执行的可能是三种操作之一,OLD/NEW 随行自适应,用它写"同步两张表并输出变更报告"这类逻辑最顺手。
相比旧方案省了什么
| 旧方案 | 痛点 | 18 的写法 |
|---|---|---|
| UPDATE 后再 SELECT 查旧值 | 两次往返,且旧值已被覆盖查不到 | RETURNING 一次拿全 |
| 触发器写审计表 | 触发器维护成本高、隐藏逻辑难排查 | 变更数据直接返回给应用决定 |
| 应用层先查后改 | 竞态窗口 + 多一次查询 | 单语句原子完成 |
观测云对照
OLD/NEW 让数据库层产出结构化变更记录变得容易,这些审计数据的下一站是统一日志平台。观测云 DataKit 采集应用与数据库日志后,可用 Pipeline 把 old_price/new_price 这类字段提取成结构化字段,在日志查看器中按产品、操作人、变更幅度检索;对异常变更(如价格单次波动超阈值)配置监控器告警,审计从"事后翻账"变成"实时发现"。 1 2 3
常见问题(FAQ)
Q:OLD/NEW 是触发器里的那个 OLD/NEW 吗?
语义一致,但这次是用在 RETURNING 子句里,不需要定义触发器。理解过触发器的人可以无缝迁移概念。
Q:用 OLD/NEW 有性能开销吗?
没有额外开销——修改前后的行在 DML 执行过程中本来就存在,RETURNING 只是把它们暴露出来,比"UPDATE + 额外 SELECT"省一次网络往返和一次查询。
Q:RETURNING 里能不能不带 OLD./NEW. 前缀?
裸列名等价于 NEW.列名(DELETE 中等价于 OLD)。但一旦要同时引用两个状态,就必须显式加前缀消歧。
Q:老版本 PostgreSQL 有什么替代方案?
触发器 + 审计表是最常见替代;或应用层"先 SELECT 再 UPDATE"(有竞态)。这也是升级 18 的一个实际理由。