PostgreSQL码农集散地

子事务是 PostgreSQL 性能杀手吗?

本期播客

子事务是 PostgreSQL 性能杀手吗?

近期看到一篇文章, 讨论 PostgreSQL 子事务, 它到底是不是性能杀手?

https://shaneborden.com/2026/02/10/do-postgresql-sub-transactions-hurt-performance/

在 PostgreSQL 的性能调优指南中,“禁用子事务”几乎是一条被写进教科书的铁律。但很多开发者不解:我只是写了一个简单的 EXCEPTION 块,或者在代码里用了几个 SAVEPOINT,怎么就成了拖垮整库的“杀手”?

今天,我们一起来撕开这个温情脉脉的语法糖包装,看看子事务是如何通过底层的 SLRU 缓存惩罚机制,一步步将你的数据库推向崩溃边缘。

一、 痛点:被误解的“安全感”

开发者最爱用的代码结构之一就是:

BEGIN
-- 执行一些业务逻辑  
EXCEPTIONWHEN OTHERS THEN
-- 报错了也不要紧,继续下一条  
END;  

现状: 这种写法在逻辑上非常健壮,但在 PostgreSQL 内部,EXCEPTION 块的本质就是开启了一个 子事务(Subtransaction) 。
后果: 每进入一次 EXCEPTION 块,系统就要消耗一个事务 ID(XID),并生成一个“保存点(Savepoint)”。
本质: 这种“安全感”是有代价的——它不仅在浪费有限的 XID 资源,更是在挑战内核共享内存的极限。

二、 技术剖析:从“内存闪电”到“磁盘泥潭”

PostgreSQL 处理子事务的逻辑遵循一个核心的第一性原理:局部性优化。

1. 64 位限制:性能的分水岭

PG 内核为每个进程提供了一个固定大小的数组(PGPROC),最多只能容纳 64 个 活跃的子事务 ID。

  • 安全区(Depth < 64): 子事务 ID 存储在快速的局部内存中,性能开销微乎其微。
  • 溢出区(Depth > 64): 一旦嵌套深度超过 64,或者单事务内累积了大量子事务,PG 必须将这些 ID 溢写到慢速的 SLRU 缓存(pg_subtrans) 中。

2. 灾难性的非线性退化

当进入 SLRU 阶段,情况会急剧恶化。

  • 锁争用: 所有的连接在检查事务状态时,都可能去争抢同一个 SLRU 缓冲池的锁(ControlLock)。
  • 数据支撑: 实验证明,在单用户模式下,溢出 64 层的子事务开销可能只增加 1% 左右;但一旦并发压力上升(如 10 个以上用户),由于锁竞争,执行时间会发生 50x-100x 的爆炸式增长。

三、 权威实测:WAL 与 XID 的“大胃王”

通过对 5000 行记录插入的基线测试,数据揭示了令人震惊的事实:

测试场景
执行耗时
WAL 写入量
XID 消耗量
性能评级
标准循环(Base)
0.02s
48 bytes
1
🟢 极快
单层 Exception
0.03s
43,936 bytes
5001
🟡 风险
溢出 128 层
3.82s
5,536,832 bytes
640,002
🔴 灾难

关键结论:

  1. WAL 暴增: 即使数据量一样,子事务为了记录“保存点”元数据,生成的日志量是普通插入的 10 万倍。
  2. XID 枯竭: 仅仅插入 5000 条数据就烧掉了 64 万个 XID。这会直接导致 Autovacuum Freeze 提前到来,甚至最严重的情况引发全库只读锁死。

四、 逻辑的崩塌:如果业务必须用呢?

前提假设: 我们假设子事务是处理“批量插入并忽略错误”的唯一手段。
条件崩塌: 如果你是在处理唯一键冲突,PG 提供了更优雅、更原生的方案:INSERT ... ON CONFLICT DO NOTHING。

引申观点: 只有在处理无法预知的非约束性错误时,EXCEPTION 才是必须的。如果逻辑崩塌,我们应该采取以下防御性编程策略:

  • 数据预校验: 在应用层或临时表中先过滤掉脏数据,而不是丢给数据库去捕获异常。
  • 批量而非逐条: 严禁在循环(LOOP)内部嵌套 EXCEPTION 块。
  • 监控真理: 不要只看 CPU,去查 pg_stat_slru。如果 blks_read 激增,说明你的子事务已经打穿了内存。

说到这, 我在这个 Skill 中包含了该监控: https://github.com/digoal/postgres_skill

结语

子事务不是洪水猛兽,但在 PostgreSQL 中,它确实是一个带刺的工具。

它在小规模、低深度的场景下优雅好用,但一旦并发升高、深度过载,它就会触发全局锁竞争,让你的数据库瞬间陷入“僵死”状态。

那么问题来了:你现在的 PL/pgSQL 存储过程里,是不是还写着那个看似万能、实则致命的 WHEN OTHERS THEN NULL?

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

议题方向:

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