32 位事务号的宿命:金仓 V9 如何用 64 位 XID 破解 PG 三十年顽疾

五年前,有开发者在博客里吐槽 PostgreSQL 的 32 位事务号(XID)是块"狗皮膏药"------贴上去勉强能用,但总在关键时刻发作。五年过去,PG 社区至今没有合并原生的 64 位 XID 方案。而在 2026 年 8 月底发布的一个国产数据库新版本中,这个被诟病多年的老问题,第一次被做成了生产可用特性。

这篇文章分两条线:一条给入门者讲清楚"PG 的 XID 到底是个啥、为什么是隐患",另一条给内核开发者一份"它改了什么、没改什么、还差什么"的对照笔记。


一、PG 的 32 位 XID 为什么是"狗皮膏药"

PG 用 uint32 存事务号,一个集群最多同时存在约 43 亿个事务号。听起来海量,但高写入业务几天就能烧光------典型电商库一天就能跑出 5 到 10 亿事务。

XID 耗尽意味着什么?PG 的 MVCC 可见性依赖 t_xmin < current_xid 判断。号不够了就得循环复用,可怎么让"老号"和"新号"在 32 位空间里不撞车?PG 的解法是 frozen xid:把 XID 空间想象成一个圆,frozen xid 是圆上一点,顺时针是"已分配(过去)",逆时针是"可分配(未来)"。每消耗约 20 亿事务,frozen xid 就要挪一次------挪的方式是让 autovacuum 扫整张表,把足够老的行的 xmin 标记成 frozen(一个特殊位),被标记的行永久可见、不再参与可见性判断。

代价很沉重:

  • 冻结风暴:全表扫描,IO 暴增、WAL 暴涨、主从延迟。
  • 长事务逼停库:autovacuum 会跳过被事务占用的 page,长事务挂一天,freeze 就推迟一天。极端时剩余 slot 不足,PG 强制进入单用户模式,拒绝一切新事务,只为把全库 freeze 跑完。
  • 运维心智负担:上线前得算清"一天多少事务、freeze 阈值配多少、SSD 扛不扛得住",算错就是生产事故。

PG 社区至今未改,理由务实:32 位 + freeze 能跑,改成 64 位要动 tuple header、CLOG、vacuum、复制协议、子事务、prepared transaction 等一大片代码,影响面太大。但 2026 年 8 月的新版本说明里明确写了:"支持 64 位事务 ID(XID),避免因 32 位事务 ID 回收不及时或长事务阻塞导致的业务中断。" 这是该系国产数据库首次把"64 位事务号"做成生产可用特性。


二、64 位 XID 改造的真实技术难点

理解这次改动的价值,先得理解 PG 为什么一直没动。它不是改个类型那么简单,牵动面极广:

  1. Tuple header 膨胀:xmin/xmax 改 64 位,每个 tuple 多 16 字节,10 亿行表仅 header 就多 16 GB------这是社区第一道犹豫。
  2. CLOG 存储瓶颈:原 SLRU 结构在 64 位下必须换(ring buffer 或 segment mmap),而动 CLOG 就动 vacuum 可见性。
  3. Vacuum 语义失效:32 位下"半个圆"的 freeze 逻辑,在 64 位下根本不需要------你一辈子写不到 2⁶³ 个事务。
  4. 子事务与 2PC :子事务、prepared transaction 都依赖 XID 命名空间,改宽后要重写 pg_subtranspg_prepared_xacts
  5. 复制协议破裂:流复制协议里的事务号字段变宽,主从必须同时升级,跨版本复制会断------这是社区最忌讳的升级门槛。

三、这次到底改了什么(有取舍的实用方案)

读版本说明书可知,这次 64 位改造并非全栈改造,而是务实的渐进方案:

  • 公开范围:支持 64 位 XID,解决 32 位回收不及时与长事务阻塞导致的业务中断。
  • 实际落点:VACUUM 参数 int64 化。四个关键参数上限从约 20 亿级扩到 2⁶⁴-1:
参数 变更前 变更后
vacuum_freeze_min_age integer,最大 ~10 亿 int64,最大 ~1.15×10¹⁹
vacuum_freeze_table_age integer,最大 20 亿 int64,最大 ~1.15×10¹⁹
vacuum_multixact_freeze_min_age integer,最大 ~10 亿 int64,最大 ~1.15×10¹⁹
vacuum_multixact_freeze_table_age integer,最大 20 亿 int64,最大 ~1.15×10¹⁹

注意:默认值完全不变 (仍是 2 亿 / 4 亿 / 100 亿 / 200 亿量级),只是扩大了上限。这释放出一个关键信号------它大概率没有改 tuple header 里的 xmin 宽度,而是"扩展分配号空间 + 让 XID 不再循环",tuple 里仍保留 32 位、仍需要 freeze。

  • 配套:实例级动态内存管控 。新增 max_dynamic_memory,全局限制实例内存,超限即告警,降低 64 位 XID 下事务生命周期变长、单次内存占用可能上升带来的 OOM 风险。
  • 协同:FastPath 锁与 CSNLOG 分区。新增 FastPath 锁机制,并可配 CLOG、CSNLOG 轻量锁分区数------64 位 XID 必然伴随 CSN 调整,分区化是高并发的必要优化。

四、给入门者的 5 个心智模型

  1. "半圆"循环 :32 位下 XID 是个圆,frozen 点是分界,顺时针是过去、逆时针是未来。64 位下,这个圆变成直线------t_xmin < 快照 直接判可见,freeze 从"对抗循环"退化为"普通清理"。
  2. freeze 是"标记"不是"删除":它把 t_xmin 换成 FrozenTransactionId=2,让所有事务都视作"早于自己",从而释放字段循环复用。64 位下该机制仍在,但触发频率大幅下降。
  3. 实际收益:消除 freeze 风暴根源、弱化 autovacuum 的强约束、解除长事务逼停库、解耦从库延迟与 freeze。
  4. 不直接解决:表膨胀(仍需 vacuum)、长事务持有的锁(仍可能堵 DDL)。
  5. 调优哲学变了:32 位下这些参数是"硬卡窗口",64 位下变成"普通 cleanup 调优",可以更激进地配置,业务也不会立刻崩。

五、专家视角的实现笔记

  • 混合方案 :本质是"分配层 64 位 + tuple header 仍 32 位",与社区讨论过的 xid8 思路一致------xid8 用于系统表、快照、WAL,tuple header 保留 32 位(用 frozen bit 标识)。好处是不破 page 格式、不破 CLOG、升级平滑、运维心智兼容;代价是 tuple 仍受 32 位"半圆"约束,单表 43 亿事务上限不变。
  • 过渡期调优 :迁移期保留 PG 经验值观察 freeze 频率;优化期可把 vacuum_freeze_min_age 调小、autovacuum_freeze_max_age 调大;平稳期再把 vacuum_freeze_table_age 放到很大,因为 freeze 已非高频操作。
  • 跨版本对比 :PG 16 的 xid8 只解决"用户能用 64 位字段",PG 17 修的是 multixact,PG 18 尚无全 64 位 XID 的 RFC。该版本是第一个把"全 64 位 XID 分配"做成生产可用的 PG 系国产库。
  • 与其他库对比:Oracle 用 SCN、MySQL/InnoDB 用 undo,都不依赖 XID 循环;zheap/zedstore 是 64 位 TID 但仍是实验项目。金仓走的是"PG 兼容 + XID 空间扩展"的中间路径,与"不破生态、只扩关键节点"的路线一致。

六、对 PG 社区的三点启示

  1. 渐进式扩展值得抄作业:"分配层 64 位 + tuple header 32 位"的分层设计,能在不破坏 page 格式的前提下把事务号空间从 43 亿扩到 2⁶⁴,代价仅多一层"分配号 → tuple xmin"映射。
  2. 默认值保留是兼容性关键:升级参数类型时默认值与 PG 完全一致,这个不起眼的细节大幅降低了 DBA 迁移心智成本。
  3. freeze 价值被重新定义:从"对抗循环的紧急任务"回归"按业务需要清理死元组",对 autovacuum 调优哲学影响深远。

七、给应用开发者的建议

  • 不要在应用层做事务号运算(事务号不连续,xid+1 无意义),64 位后这点依旧成立。
  • freeze 不再是紧急任务,告警阈值可以放宽,给运维留更多空间。
  • 跨版本升级测试更复杂,主从 XID 兼容性需要专门验证。

八、写在最后

五年前的那句吐槽,当时没人接。PG 社区认为 freeze + autovacuum 够用,国产库也没人敢碰这块。如今这个版本把 64 位 XID 做成了生产可用,完成了社区没完成的事。

它提醒我们:国产数据库不只在做兼容,也在啃内核里那些连上游都回避的硬骨头。这条"32 位 → 64 位 XID 的渐进改造"路径,值得后来者认真参考。

五年吐槽,一个版本号,问题被治好了。

相关推荐
晚安日记wanna2 小时前
一条 SMEMBERS 干瘫 Redis 节点:单线程的真正边界在哪
redis·后端·面试
码事漫谈2 小时前
在 Kubernetes 上管好数据库:金仓 KES-Operator 正式落地
前端·后端
小蒜学长2 小时前
基于SpringBoot+Vue的游戏论坛系统的设计与实现(代码+数据库+LW)
java·后端·springboot·游戏论坛系统·社区生态
SimonKing2 小时前
文档杂乱怎么查找:用 Papra 搭一个极简文档管理系统
java·后端·程序员
掘金者阿豪2 小时前
金仓、MySQL、PostgreSQL 用什么管理工具?DBeaver、Navicat、KStudio 我都试了一遍
后端
雨辰AI2 小时前
多租户数据库资源配额管控|避免租户资源抢占雪崩(金仓 / 达梦 / 高斯 /openGauss 全库原生适配)
java·大数据·数据库·后端
zdr2 小时前
极空间 NAS 没有命令行,我逆向了它的桌面客户端
后端
掘金挖土2 小时前
前端手摸手跑路之 AI 应用开发(七)
前端·后端
我也要在julius_bar里藏钱2 小时前
【山竹记账后端】1.搭建后端项目
后端·ruby·rails