五年前,PostgreSQL 32位事务号带来的运维痛点,一直是高写入业务场景中难以绕开的难题。而在今天,这一长期困扰数据库从业者的历史顽疾,在国产金仓数据库中迎来了突破性解决方案。本文一方面面向数据库入门读者,通俗拆解PostgreSQL事务号XID底层原理;另一方面,面向PG内核研发人员,完整剖析金仓本次64位事务号特性的改造内容、实现思路,同时客观梳理当前方案的能力边界,作为可落地的技术参考文档。
目录
一、为什么PG的32位XID,长期被视为数据库运维的顽固难题
二、64位XID内核改造,背后真实存在的技术壁垒
三、金仓V009R002C016版本,64位XID改造的实际覆盖范围
3.1 官方文档明确的能力描述
3.2 核心改动:VACUUM相关参数升级为int64类型
3.3 配套改造:实例级动态内存管控
3.4 协同优化:FastPath锁机制升级
四、面向入门读者:5个核心知识点,快速理解64位XID
4.1 32位XID的圆环循环模型
4.2 freeze冻结到底在做什么
4.3 64位XID带来的真实业务收益
4.4 autovacuum调优逻辑发生本质变化
4.5 和主从复制、备份恢复的关联
五、面向内核专家:底层实现细节深度解读
5.1 数据类型层面的工程取舍
5.2 32位升级64位,autovacuum分阶段调优建议
5.3 和PG 16/17/18版本横向对比
5.4 多数据库横向对比
六、金仓本次改造,带给PG社区的启发
6.1 渐进式XID扩展方案
6.2 默认参数不变的兼容性设计
6.3 重新理解freeze的定位
七、面向应用开发者的实践建议
八、对金仓后续迭代的两点期待
九、写在最后
一、为什么PG的32位XID,长期被视为数据库运维的顽固难题
PostgreSQL内部采用32位无符号整数存储事务编号,TransactionId本质上是uint32数据类型。从数值上限来看,整个数据库集群最多能够分配约43亿(2³²)个事务号。单看这个数字,体量似乎十分庞大,但在高吞吐写入的业务场景下,事务号消耗速度会远超预期。典型电商业务数据库,单日事务产生量就可以达到5亿至10亿级别,事务号会快速耗尽。
一旦事务号耗尽,就会触发底层MVCC可见性判断机制的核心矛盾。PG的多版本可见性判断规则依靠t_xmin < current_xid逻辑完成。当全部32位空间内事务号分配完毕,新事务无法获取全新编号,事务号就必须开启循环复用。随之而来的核心难题:如何在有限的32位数值空间内,区分旧事务号与新复用的事务号,避免版本冲突、可见性逻辑错乱?
PostgreSQL社区给出的经典方案就是冻结事务号(frozen xid)。简单理解,PG把整个XID空间想象成一个圆环,frozen xid是圆环上的分界点。顺时针方向代表已经分配完成、属于过去的事务编号;逆时针方向代表还未分配、可供新事务使用的事务号。每当消耗约20亿事务号,frozen xid分界点就必须向前移动。移动分界点的执行逻辑,依靠autovacuum自动清理进程扫描全表,将所有t_xmin < frozen_xid的数据行头信息内的xmin打上frozen冻结标记。被标记冻结的数据行,会被认定为永久可见,不再参与后续的事务可见性对比判断。
这套原生机制,在生产环境带来了沉重且无法忽视的运维代价。 第一,冻结风暴风险。执行全表扫描冻结操作时,会瞬间拉高磁盘IO压力,WAL日志量暴涨,直接引发主从复制延迟,严重影响业务读写性能。 第二,长事务会造成强制停库冻结风险。autovacuum会跳过正在被长事务占用的数据页,只要长事务持续运行一天,冻结操作就会持续延后。极端场景下,pg_xact系统表剩余可用slot不足100万时,PG数据库会强制切换至单用户模式,拒绝接收所有新事务,只为完成全库冻结,直接造成业务中断。 第三,极高的运维心智负担。数据库上线部署阶段,DBA必须提前测算业务每日事务产生量、合理配置冻结阈值,评估SSD磁盘能否承受冻结带来的瞬时IO冲击。任何参数估算失误,都有可能直接引发生产事故。
早在2021年8月24日,我在博客《DB吐槽大会第2期 – PG 32位 xid》中就专门阐述过该问题:事务号XID为uint32类型,上限仅40亿,事务号必须循环复用。同时在文中提出优化方向,建议未来数据库版本迭代时,引入64位XID方案,类似zedstore、zheap、postgrespro等存储引擎的设计思路。
五年时间匆匆而过,PostgreSQL社区至今没有正式合入完整64位XID内核方案。参考2026年PG邮件列表的社区共识:虽然PG已经存在xid8数据类型,但数据库内核内部使用的TransactionId事务号依旧是uint32。社区选择保留原有方案的理由十分现实:32位XID搭配frozen冻结机制尚且可以正常运行;如果全面升级为64位事务号,需要修改tuple头部、CLOG提交日志、vacuum清理逻辑、流复制协议、子事务、两阶段提交事务、哈希连接等大量依赖XID的底层模块,改动范围极大,风险难以评估。
而2026年8月28日发布的金仓V009R002C016版本,在版本文档中明确标注:支持64位事务ID(XID),能够规避32位事务ID回收不及时,或是长事务阻塞冻结流程而引发的业务中断故障。
这也是国产数据库领域,第一次将64位事务号能力打磨至生产可用级别,其技术价值,不亚于PostgreSQL15版本实现的逻辑复制跨大版本兼容能力。
二、64位XID内核改造,背后真实存在的技术壁垒
想要读懂金仓本次的技术实现,首先要理解为什么PG社区多年迟迟不敢落地全量64位XID改造。将事务号从32位升级至64位,绝非简单修改一个变量类型,内核多个核心模块都会受到牵连,改造难点分布在多个底层组件:
1.Tuple头部存储空间膨胀问题 PG原生HeapTupleHeaderData结构简化定义如下:
typedef struct HeapTupleHeaderData {
t_choice t_choice; // t_xmin存储模式
TransactionId t_xmin; // 32位事务号
TransactionId t_xmax; // 32位事务号
CommandId t_cid;
TransactionId t_xvac; // 32位,PG17版本已移除
TransactionId t_multi; // multixact多事务ID,32位
ItemPointerData t_ctid;
};
如果直接将xmin、xmax、multi全部升级为64位,每一条tuple元组头部就要额外增加16字节。对于一张拥有10亿行数据的大表,仅元组头部就要额外占用16GB存储空间,这也是PG社区最大的顾虑之一。
2. CLOG提交日志无法继续沿用SLRU存储架构 PG的CLOG(Commit Log)用来记录每一笔事务的提交状态,采用8KB页存储,每页可存放512个事务状态,依靠SLRU两级缓存管理。切换到64位XID后,原有CLOG存储结构不再适用。行业内有两种改造思路:环形缓冲区,或是基于分段mmap文件的存储方案。PG社区多次讨论该方向,但始终没有落地,原因在于改动会深度影响vacuum的可见性判断逻辑。
3. Vacuum的半圆判断语义失效 32位XID架构下,vacuum通过relfrozenxid < oldestXid – 2000000000进行判断,核心逻辑就是判定半圆范围内的旧事务需要执行冻结。一旦升级为64位XID,事务号空间几乎无限,业务几乎无法耗尽2⁶³个事务,原有的半圆循环机制彻底失去意义。这就需要对冻结逻辑进行完全重构,目标从“对抗事务号循环耗尽”转变为“常规历史数据清理”。
4. 子事务与两阶段提交事务的连锁影响 PG子事务号同样基于uint32实现,复用父事务XID命名空间。升级64位之后,子事务需要独立命名空间,常见方案是高32位存放进程编号,低32位存放子事务编号,pg_subtrans系统表需要重写。两阶段提交prepared transaction持久化存储的XID,跨实例持久化时要保证64位宽度,pg_prepared_xacts系统表Schema也需要改动。
5. 流复制协议变更 流复制协议WalDataMessage消息内部携带xl_xid事务号字段。升级64位之后,主库与从库必须同步升级版本,跨版本主从复制链路会直接中断,这是PG社区十分谨慎的升级门槛。
三、金仓V009R002C016版本,64位XID改造的实际覆盖范围
研读金仓V009R002C016版本说明书PDF文档(L903、L1012-1075章节),本次64位XID改造并非激进的全栈重构,而是兼顾兼容性与稳定性的工程取舍方案。
3.1 官方文档明确的能力描述
版本文档L903原文:支持64位事务ID(XID),避免因32位事务ID回收不及时或长事务阻塞导致的业务中断问题。 L1012章节写明:为适配64位事务ID,VACUUM以及自动清理Autovacuum相关参数的数据类型、取值范围同步升级。
3.2 核心改动:VACUUM相关参数升级为int64类型
文档中列出四项关键参数的变更:
| 参数 | 修改前 | 修改后 |
| vacuum_freeze_min_age | integer,上限10亿 | int64,上限2⁶⁴-1(约1.15×10¹⁹) |
| vacuum_freeze_table_age | integer,上限20亿 | int64,上限2⁶⁴-1 |
| vacuum_multixact_freeze_min_age | integer,上限10亿 | int64,上限2⁶⁴-1 |
| vacuum_multixact_freeze_table_age | integer,上限20亿 | int64,上限2⁶⁴-1 |
值得注意:这一组参数的默认配置值保持不变,依旧沿用原有200000000 / 400000000 / 10000000000 / 20000000000,仅仅扩大了参数上限。 这个设计代表金仓内置了一套32位XID语义仿真层。原有PG生态的业务脚本,冻结阈值配置完全不需要修改,默认行为和PostgreSQL保持一致。 从工程层面解读这个信号:金仓并没有修改tuple header内部的xmin字段为64位。如果元组头部xmin改为64位,冻结相关参数的语义会发生根本性变化。本次64位XID改造,采用的是扩展事务号分配空间,消除XID循环复用的方案,tuple header内部t_xmin依旧保留32位,仍然依靠frozen标记完成旧事务处理。
3.3 配套改造:实例级动态内存管控
版本文档L903紧接着说明:全面支持64位事务ID与实例级动态内存管控,新增max_dynamic_memory参数,可以全局限制数据库实例内存占用。当内存超出阈值时,系统会触发明确告警,降低OOM内存溢出风险。
这是64位XID改造必要的配套能力。64位事务号拉长了事务号生命周期,不再循环复用,autovacuum后台冻结任务执行频次下降,后台清理压力减轻。但长事务会持有更大的XID缓存,单次事务内存占用存在上升可能性。金仓同步增加全局内存上限管控,规避64位XID改造带来的新增OOM风险。
3.4 协同优化:FastPath锁机制升级
文档L1299提到:新增FastPath锁机制,支持配置FastPath槽位数量、CLOG轻量锁分区数、CSNLOG轻量锁分区数。
CSNLOG即Commit Sequence Number Log,记录事务提交顺序。过去CSN为32位,跟随XID循环。64位XID改造必然同步调整CSN逻辑。金仓同步把CSNLOG的全局单锁改造为可配置轻量分区锁,提升高并发场景下锁竞争性能。
四、面向入门读者:5个核心知识点,快速理解64位XID
如果你刚刚接触PostgreSQL体系数据库,本节搭建基础认知模型,快速看懂底层逻辑。
4.1 32位XID的圆环循环模型
32位架构下事务号如同圆环:

每一条元组t_xmin落在圆环上的某一个位置。可见性判断伪代码:
if t_xmin == frozen_xid:
永久可见
elif t_xmin 在 frozen_xid 顺时针一侧:
属于过去事务,一般不可见
else:
属于未来事务,查询提交日志判断是否提交
升级64位XID之后,环形结构转变为无限延长的直线。直接使用t_xmin < 当前事务快照判断可见性,不再存在循环逻辑。冻结操作,也从解决循环危机,转变为常规数据清理工作。
4.2 freeze冻结到底在做什么
freeze并不会删除历史数据,只是对元组行头打上标记。PG在heap tuple header的t_infomask预留两个bit位:HEAP_XMIN_FROZEN、HEAP_XMIN_INVALID。执行freeze时,将t_xmin替换为FrozenTransactionId=2,所有事务都会识别这条记录是冻结状态,代表该记录早于所有事务,t_xmin字段释放出来,用于循环复用。
金仓本次方案中,这套标记机制仍然保留,根源是tuple header字段宽度没有改动。但是触发冻结的频率会大幅下降。XID不再循环,frozen分界点不再是圆环一半位置,仅用于清理业务长期留存的超历史数据。
4.3 64位XID带来的真实业务收益
高写入业务场景,64位XID直接根除一系列重大风险:
-
消除冻结风暴根源,不再需要担心事务号接近半圆耗尽的紧急风险
-
autovacuum不再有必须完成冻结的硬性约束,vacuum执行缓慢也不会直接引发宕机
-
长事务不再挤占冻结空间,杜绝长事务引发强制停库冻结的极端故障
-
从库不再跟随主库冻结操作产生巨大延迟,主从延迟和冻结操作解耦
但需要客观区分能力边界,64位XID不能解决下面问题:
-
表膨胀bloat,仍然依靠vacuum/autovacuum清理死元组
-
长事务持有的锁资源,依旧会阻塞DDL语句执行
4.4 autovacuum调优逻辑发生本质变化
32位XID时代,autovacuum_vacuum_insert_scale_factor、vacuum_freeze_table_age、vacuum_freeze_min_age属于硬性时间窗口约束,必须在消耗20亿事务号前完成冻结。 64位XID场景下,这些参数转变为常规清理策略配置。DBA可以选择更加激进的清理策略,就算冻结执行不及时,数据库也不会立刻宕机。金仓保留原有参数默认值,就是为了让原有PG运维经验可以平滑复用,降低DBA学习成本。
4.5 和主从复制、备份恢复的关联
32位XID架构下,备库standby同样需要计算xmin,同样会遭遇冻结风暴。升级64位XID之后,备库冻结压力显著降低。但备库切换、主从切换场景,依旧需要校验主从XID版本兼容性。金仓版本文档没有针对这部分详细说明,推测该模块尚未全部完成改造,或是采用主从实例必须保持相同版本的限制策略,类似PG大版本升级的约束。
五、面向内核专家:底层实现细节深度解读
如果你是PG内核开发者、资深DBA,本节相当于金仓本次改造的实现笔记。
5.1 数据类型层面的工程取舍
基于版本文档反向推导,金仓内部XID定义大致如下:
typedef uint64 kingbase_xid; // 64位用于事务号分配
typedef uint32 kingbase_tup_xmin; // tuple header内部xmin依旧32位
typedef uint32 kingbase_tup_xmax; // tuple header内部xmax依旧32位
也就是分配层使用64位,元组头部维持32位的混合方案,和PG社区讨论的xid8思路相似:xid8用于系统表、事务快照、WAL日志,tuple header保持32位,依靠frozen bit标记冻结xmin。
这套方案优势明显:
无需改动tuple header大小,原有数据页完全兼容,存量数据不用迁移
CLOG原有存储结构不用重构
版本升级路径平滑,主从节点不需要同时一次性升级
运维参数默认行为不变,降低DBA迁移成本
对应的代价:
tuple header依旧依赖freeze机制,仅仅降低触发频次
单表受限于tuple header32位,仍然存在43亿事务的上限约束
5.2 32位升级64位,autovacuum分阶段调优建议
已经部署金仓V009R002C016版本的DBA,可以采用三阶段调优策略:
— 第一阶段:迁移过渡期(1~3个月)
— 维持原有PG经验配置,不修改freeze参数,观察冻结频率
— 第二阶段:优化期(3~6个月)
ALTER SYSTEM SET vacuum_freeze_min_age = 50000000;
ALTER SYSTEM SET autovacuum_freeze_max_age = 1000000000;
— 第三阶段:稳定运行期(6个月以上)
— 根据业务负载调优,可将freeze_age设置至更大数值
ALTER SYSTEM SET vacuum_freeze_table_age = 5000000000;
5.3 和PG 16/17/18版本横向对比
PG16引入xid8数据类型,仅仅支持用户表存储64位事务字段,数据库内核内部事务分配号依旧是32位。 PG17对transaction_id相关优化集中在multixact多事务问题,没有修改核心XID。 PG18邮件列表虽然讨论全量64位XID方案,但是改动风险过高,至今没有正式RFC。 金仓V9的价值,是业内首个把64位XID事务号分配能力落地至生产环境的PG系国产数据库。
5.4 多数据库横向对比
| 数据库 | 事务号宽度 | freeze机制 | 备注 |
| PostgreSQL | 32位 | freeze + autovacuum | 32位XID循环复用 |
| Oracle | SCN内部6字节 | undo +多版本 | 不存在XID循环问题 |
| MySQL InnoDB | 6字节TRX_ID | undo log | 不存在XID循环问题 |
| KingbaseES V9 | 64位分配层 | 混合方案,tuple header32位 | 国产首个生产可用64位XID |
| zheap / zedstore | 64位TID | 无需freeze | 仅实验项目,未生产落地 |
横向对比可以看出,金仓选择的是「PG原生生态兼容 + 扩展XID分配空间」渐进路线,没有像zheap一样彻底重写tuple header。这和金仓长期坚持的Oracle兼容路线一脉相承:不破坏现有生态,针对核心痛点做底层扩展。
六、金仓本次改造,带给PG社区的启发
金仓的64位XID工程实践,给PostgreSQL社区提供三条有参考价值的实现思路。
6.1 渐进式XID扩展方案
PG社区讨论全量64位XID最大顾虑就是破坏tuple header兼容性。金仓采用分配层64位、元组头部保留32位的分层方案,在不改动page存储格式的前提下,将事务号空间从43亿提升至2⁶⁴,仅增加一层分配号映射到tuple xmin的逻辑,这条渐进路线值得PG社区参考。
6.2 默认参数不变的兼容性设计
升级冻结参数时,保持原有默认值不变,仅仅扩大参数上限。这个细节降低DBA迁移的心智负担,是非常优秀的兼容性设计。如果PG社区未来推进同类改造,这种思路具备很高参考价值。
6.3 重新理解freeze的定位
32位XID时代,freeze是用来解决事务号循环的紧急运维任务;64位XID下,freeze回归为常规数据清理动作。语义的转变,会彻底改变autovacuum调优的底层思路。DBA不再需要精密计算冻结窗口,只需要根据业务需求清理死元组。
七、面向应用开发者的实践建议
完成64位XID改造之后,应用侧开发规范也需要对应调整:
不要在业务代码中直接对事务号做数值运算。PG系数据库事务号本身不连续,事务提交后会出现跳号,业务层执行xid+1这类逻辑没有意义。金仓64位XID版本该特性不变。
freeze不再属于紧急高危任务,监控告警阈值可以适度放宽。可以把告警阈值从剩余1000万事务,调整到剩余100万事务,给DBA预留充足运维窗口。
跨版本迁移测试复杂度提升。老版本升级至支持64位XID的新版本,必须完整测试主从之间XID版本兼容性。
八、对金仓后续迭代的两点期待
作为长期使用PG的技术从业者,针对该特性,我对金仓有两点期待: 第一,完善配套运维最佳实践文档。清晰区分哪些传统冻结相关配置不再需要精细测算,同时梳理新版本新增的运维注意事项。 第二,补充配套监控指标。虽然max_dynamic_memory已经加入告警体系,但是64位XID场景下,事务平均生命周期是极具价值的指标,方便DBA直观评估升级之后的实际效果。
九、写在最后
五年前,我撰文吐槽PG 32位XID是难以根治的数据库顽疾。彼时PG社区认为32位XID搭配autovacuum冻结机制足以支撑业务,国内国产数据库厂商也没有涉足这块高难度内核改造。 而金仓V009R002C016版本,将64位XID做到生产可用。这件事证明国产数据库不只是在做Oracle语法兼容,也敢于攻克PostgreSQL社区长期搁置的内核底层难题。 金仓这套从32位到64位XID的渐进式改造方案,值得PG17、18、19版本社区认真参考借鉴。 历时五年的行业痛点,在一个版本迭代中,被国产数据库给出了解决方案。
附录:参考文献
-
digoal 博客《DB 吐槽大会第 2 期 – PG 32 位 xid》(2021-08-24): https://github.com/digoal/blog/blob/master/202108/20210824_01.md
-
金仓 V009R002C016 版本说明书 PDF(2026-08-28 发布): 金仓数据库管理系统KingbaseES_V9版本说明书.pdf L838 / L903 / L1012-1075
-
PostgreSQL 官方 TransactionId 定义: src/include/c.h(uint32 类型)
-
PG 16 xid8 数据类型: src/backend/utils/adt/regproc.c + src/include/catalog/pg_type.dat
网硕互联帮助中心




评论前必须登录!
注册