PostgreSQL 19 目前处于 Beta 阶段(Beta 1 于 2026 年 6 月 4 日发布,Beta 2 于 7 月 16 日发布),正式 GA 日期以官方公告为准。这是做迁移准备最划算的窗口:破坏性变更已经定型、可以照着逐条排查,但你还没有把生产压上去。本文按「哪些会让 pg_upgrade 直接拒绝、哪些会静默改变行为、哪些要重新规划配置」把八个不兼容变更分类,再谈值得为之升级的新能力和一份可执行的 Beta 测试计划。
Beta 阶段该做什么,不该做什么
不该做的很明确:不要把 Beta 用于生产数据。Beta 的意义在于让你提前发现自己的代码会在哪里坏掉,而不是提前享受新特性。
该做的是三件事。第一,用生产结构和一份脱敏数据在 Beta 上跑一遍你的完整测试套件——这比读一百遍 release notes 更能发现问题。第二,逐条核对下面的破坏性变更,判断哪些命中了你。第三,把 pg_upgrade 的预检跑起来,因为其中两个变更会让升级直接被拒绝,越早知道越好。
官方发布说明写得很清楚:从任何早期版本迁移数据,都需要用 pg_dumpall 做 dump/restore,或者使用 pg_upgrade,或者走逻辑复制。没有原地升级这一说。
八个破坏性变更
会让 pg_upgrade 直接拒绝的两个
inet/cidr 的默认索引 opclass 从 btree_gist 改为 GiST。 官方给出的理由很硬:btree_gist 的 inet/cidr opclass 是有缺陷的——它可能排除掉本应返回的行。也就是说,如果你现在有这类索引,你的查询结果可能一直就是错的。pg_upgrade 会拒绝升级含有 btree_gist inet/cidr 索引的集群。
这一条应该立刻去查,不是等升级时再说。有网段字段并建了 GiST 索引的表尤其要看。
库名、角色名、表空间名中禁止回车和换行。 出于安全考虑,pg_upgrade 同样会拒绝使用了这类名字的集群。听起来像不可能发生,但自动化脚本拼接名字时把换行带进去是真实存在的事故。一条查询就能确认。
会静默改变行为的三个
这三个不报错,只是行为变了——最危险的一类。
JIT 默认关闭。 之前 JIT 默认开启并按优化器代价自动触发,官方认定这套代价估算不可靠,于是改为默认关闭。做大量分析型查询的站点必须手动重新开启。如果你的报表查询依赖 JIT 提速,升级后会突然变慢,而且没有任何报错提示你原因。
默认 TOAST 压缩从 pglz 改为 lz4。 通过 default_toast_compression 生效。新写入的大字段会用 lz4,已有数据保持原样。lz4 压缩率略低但快得多,多数场景是净收益;但如果你的存储容量卡得很紧,压缩率下降需要提前算进容量规划。
max_locks_per_transaction 默认从 64 改为 128。 关键在于官方的说明:锁的空间分配方式变了,原有配置值需要翻倍才能达到之前的容量。如果你显式配置过这个值(比如分区表很多的场景通常会调大),升级时不能原样搬过去,否则实际锁容量会腰斩,表现为莫名其妙的 "out of shared memory" 错误。
需要重新规划的三个
standard_conforming_strings 被强制为 on。 服务端不再允许关闭。影响面在于旧 dump:用 PostgreSQL 19 之前版本的 pg_dump/pg_dumpall 且 standard_conforming_strings = off 生成的 dump,无法正确载入 19 及以后的服务端。官方给的做法是用 19 或更新版本的这两个工具重新生成 dump,或者确保生成时该参数为 on。
有归档 dump 用于灾备的团队要注意:那些文件可能已经无法用于恢复到新版本了。这件事应该纳入你的恢复演练。
移除 RADIUS 认证。 理由是 PostgreSQL 只支持 UDP 上的 RADIUS,而那是无法修复的不安全。用了 radius 认证方法的必须先迁到别的方案,这不是配置调整而是认证架构变更,要留出时间。
移除 MULE_INTERNAL 编码。 使用该编码的数据库需要用其它编码 dump 并重新导入。用到的人不多,但用到了就是一次完整的数据迁移。
升级路径怎么选
| 路径 | 停机 | 适用 |
|---|---|---|
pg_dumpall + restore |
长,与数据量成正比 | 小库;或必须换编码、换排序规则时 |
pg_upgrade |
短 | 大库的默认选择 |
| 逻辑复制 | 最短,可回切 | 停机窗口极紧、且能承担双写复杂度 |
pg_upgrade 是大多数情况的答案,但前面那两个「会被拒绝」的检查必须先过。先在非生产环境跑一次带 --check 的预检,它会把阻断项列出来——这一步不需要停机,随时可以做。
逻辑复制的价值在于可回切:新旧版本同时在跑,出问题切回去就行。代价是要处理序列、DDL 不复制、以及初始同步期间的负载。PostgreSQL 19 增加了逻辑复制对序列的支持,这一点降低了它的门槛。
走这条路时,「断点续传」的语义值得单独设计:复制中断后从哪里接着来、位点丢了怎么办、重复投递如何在下游幂等,这些问题跨数据库是共通的。我们在MongoDB Change Streams 断点续传实战里讨论过 resume token 失效与重放边界的处理方式,那套「先保证位点可恢复、再保证下游幂等」的思路同样适用于逻辑复制槽。
值得为它升级的新能力
REPACK 统一了 VACUUM FULL 和 CLUSTER。 这两个命令做的事情本来就相似,只是名字令人困惑,19 把它们统一成 REPACK(旧命令保留以兼容)。更实际的是 CONCURRENTLY 选项:重建表时不需要 access-exclusive 锁,配套新增了 max_repack_replication_slots 参数。
这解决的是一个长期痛点。以前想回收膨胀表的空间,VACUUM FULL 会全程锁表,只能安排在维护窗口;现在可以在线做。如果你有那种「明知道膨胀但一直不敢动」的大表,这一条本身就值回升级成本。
并行 autovacuum。 通过 autovacuum_max_parallel_workers 控制上限,还可以用表级存储参数 autovacuum_parallel_workers 单独设置。写入密集的大表上,autovacuum 跟不上是常见的运维噩梦——清理速度赶不上产生垃圾的速度,膨胀持续恶化。并行化直接针对这个问题。
pg_plan_advice。 用于稳定和控制规划器的决策。执行计划突然翻车、而你只想让它保持原样时,以前只能靠调 enable_* 参数或者改写 SQL 兜圈子。
其它值得一提的:INSERT ... ON CONFLICT DO SELECT ... RETURNING 可以返回冲突的行并选择用 FOR UPDATE/FOR SHARE 加锁;GROUP BY ALL 自动按所有非聚合、非窗口函数的目标列分组;COPY TO 支持 JSON 输出(配合 FORCE_ARRAY 可输出单个 JSON 数组);UPDATE/DELETE 新增 FOR PORTION OF 支持时态区间操作;窗口函数 lead()、lag()、first_value()、last_value()、nth_value() 支持 IGNORE NULLS/RESPECT NULLS;以及 SQL/PGQ 属性图查询。
一份可执行的 Beta 测试计划
- 先跑阻断项检查。 查 btree_gist 的
inet/cidr索引,查名字里带回车换行的库/角色/表空间。这两项不解决,后面都白做。 - 搭一套 Beta 环境,用生产的 schema 加脱敏数据,不用生产数据。
pg_upgrade --check跑通,把它输出的每一条都处理掉。- 跑完整测试套件,重点看依赖字符串字面量转义、依赖 JIT 性能、以及有大量分区表的路径。
- 对比执行计划。 关掉 JIT 后分析型查询的计划和耗时会变,先量出来再决定是否手动开启。
- 验证备份恢复链路。 用 19 的
pg_dump重新生成一份 dump 并恢复,确认归档策略在新版本下仍成立。 - 压测锁密集场景。 如果显式配过
max_locks_per_transaction,按翻倍原则调整后再测。 - 记录回滚判据。 明确到什么指标退化就中止升级,而不是临场判断。
第 4 到 7 步适合做成定期任务而不是一次性动作——Beta 到 GA 之间还会有变化,每次新 Beta 出来重跑一遍才有意义。这类「定期跑一组有依赖关系的任务并在失败时通知」的活,用调度器比堆 cron 清楚得多,我们自己的 Cronova 就是干这个的:单二进制自托管,YAML 定义 DAG,内置 SQLite、Web Console 和 REST API,重试和通知都在调度层。
Beta 版本迁移这套方法论本身是通用的。Python 3.15 Beta 的迁移我们写过一篇依赖、ABI 与上线门禁的处理指南,那里的分阶段验证思路和这里完全可以互相套用——先找阻断项,再找静默行为变化,最后才是新特性。
常见失败模式
照搬旧配置。 max_locks_per_transaction 是最典型的:数值没变但含义变了,原样搬过去等于把容量砍半。升级时应该逐项复核而不是整份 copy。
忘了 JIT 已经关掉。 分析型查询变慢,团队去查索引、查统计信息、查硬件,就是想不到是默认值变了。升级后第一件事应该是记录关键查询的基线耗时。
旧 dump 无法恢复。 归档里那些几年前的 dump,如果当时 standard_conforming_strings = off,现在载不进 19。备份的价值在于能恢复,不在于文件还在。
在 Beta 上积累了生产数据。 Beta 到 GA 之间可能有不兼容的 catalog 变更,届时只能重建。Beta 环境要按「随时可丢弃」来设计。
只测了功能没测运维。 新版本的 autovacuum 行为、锁行为、压缩行为都变了,这些不会在功能测试里暴露,只会在生产负载下暴露。
迁移检查清单
- 已确认集群中不存在 btree_gist 的
inet/cidr索引,或已计划重建 - 已确认库名、角色名、表空间名中不含回车换行
pg_upgrade --check在非生产环境跑通,输出的每一项都已处理- 显式配置过的
max_locks_per_transaction已按翻倍原则重新计算 - JIT 关闭后的分析型查询耗时已量化,是否手动开启是基于数据的决定
- 归档 dump 的可恢复性已验证;必要时用 19 的
pg_dump重新生成 - 使用
radius认证方法的连接已迁移到其它认证方案 - 使用 MULE_INTERNAL 编码的数据库已规划编码迁移
- 应用侧字符串字面量转义已复核,不依赖
standard_conforming_strings = off - 存储容量规划已计入 TOAST 由 pglz 改为 lz4 后的压缩率变化
- 回滚判据已明确,且升级路径支持该回滚方式
常见问题
PostgreSQL 19 什么时候 GA? 官方按每年一个大版本的节奏发布,具体日期以官方公告为准。当前是 Beta 阶段,Beta 1 发布于 2026 年 6 月 4 日、Beta 2 发布于 7 月 16 日。
可以原地升级吗? 不能。官方明确要求用 pg_dumpall 做 dump/restore、使用 pg_upgrade、或走逻辑复制。
JIT 为什么被关掉了? 官方说明是:此前按优化器代价自动激活 JIT,而这套代价估算被认定为不可靠。需要的站点手动开启即可,能力本身没有移除。
已有数据会自动转成 lz4 吗? 不会。default_toast_compression 影响的是新写入的数据,已有 TOAST 数据保持原有压缩方式。
REPACK 会取代 VACUUM FULL 吗? REPACK 是统一后的新命令,VACUUM FULL 和 CLUSTER 为兼容保留。新代码用 REPACK,它还多了 CONCURRENTLY 这个旧命令没有的能力。
为什么 pg_upgrade 拒绝我的集群? 最可能是两个阻断项之一:存在 btree_gist 的 inet/cidr 索引,或者库/角色/表空间名里有回车换行。先跑 --check 看具体报告。