Skip to content

版本升级概述 ​

PostgreSQL 的小版本(minor release,如 17.2 → 17.4)只包含缺陷修复与安全补丁,升级只需替换二进制后重启;而大版本(major release,如 16 → 17)的数据文件格式不保证兼容,物理层的东西——数据文件、WAL、复制协议——都无法跨大版本直接使用。这就是「大版本升级」成为运维领域一个专门课题的原因:它必然涉及「如何把数据搬到新格式」这一根本问题,而每一种搬运方式都对应着不同的停机时间、资源消耗与风险面。

选型一个升级方案,本质上是在四个维度上做权衡:停机窗口(业务能接受多久只读或不可用)、数据规模(百 GB 与 10 TB 的可行方案完全不同)、回滚难度(升级失败后多久能回到旧版本)、前期成本(双倍磁盘、双倍算力、复杂度)。没有一种方案在四个维度上全赢:pg_upgrade 最快但要有窗口;逻辑复制几乎零停机但要全程运维一条复制链路;pg_dumpall 最简单但大库跑不动。本页的任务就是把主流方案的坐标系摆清楚,让你按自身约束对号入座。

本页是「升级两部曲」的第一页:先把方案全景、版本策略(跨大版本 vs 小版本)、升级前检查与回滚设计讲透;下一页 pg_upgrade 实战 再深入最常用方案 pg_upgrade 的原理、参数与逐命令实操。两页合起来,覆盖从「选方案」到「落地执行」的完整路径。

前置知识

建议先阅读:WAL 积压监控

基础概念 ​

大版本与小版本的本质区别 ​

维度小版本升级(17.2 → 17.4)大版本升级(16 → 17)
数据文件格式兼容不保证兼容
WAL / 复制协议兼容不兼容(standby 必须与主库同大版本)
升级方式替换二进制 + 重启需要数据迁移(本页主题)
停机秒级重启分钟级(pg_upgrade)到近零(逻辑复制)
pg_catalog微调系统目录结构可能变化
频率建议安全补丁尽快跟进按特性需求规划,通常每年 ~1 次

小版本升级的纪律只有一条:认真看 Release Notes 的「Migration to Version X.Y」小节,个别小版本会要求额外的步骤(如 PG13.x 时代个别版本曾建议重建特定索引)。除此之外就是「替换二进制 → 重启 → 验证」,且主备架构下可以先升级备库再切换,实现滚动升级。

大版本升级为什么必须「搬数据」 ​

PostgreSQL 的物理存储中包含大量与版本绑定的内容:系统目录的物理布局、堆页内元组头的字段、WAL 记录格式、复制协议消息。大版本发布时这些内部结构允许变化,于是旧版本写下的数据文件,新版本无法按旧语义读取。所有大版本升级方案,无论包装成什么名字,本质都是四种「搬法」之一:

text
 ① 元数据重建 + 文件复用    → pg_upgrade(在新版本集群里重建系统目录,
                              把旧数据文件当作新集群的数据文件用)
 ② 重新解释并重放           → 逻辑复制(把变更解码成逻辑记录在新库重放)
 ③ 全量导出再导入           → pg_dump / pg_dumpall(中间产物是 SQL/COPY 文本)
 ④ 物理拷贝后再处理         → 流复制 + 切换(受大版本限制,见下文)

五种主流方案对比 ​

方案停机时间资源成本复杂度回滚适用规模 / 场景
pg_upgrade分钟级(数据拷贝模式更久;--link 模式接近分钟级)双倍磁盘(拷贝模式)或共享 inode(link 模式)中好(保留旧目录)中小规模,可接受分钟级窗口;最常用
逻辑复制迁移近零停机(仅切换瞬间)双倍算力 + 双倍存储 + 复制链路运维高好(可双向/回切)大规模、要求业务无感的场景
pg_dumpall 重建小时级起,随规模线性增长双倍存储 + 导出临时空间低好小库、测试环境、顺带重构结构时
流复制 + 切换分钟级双倍存储低~中好见下方说明:受大版本兼容性限制
双跑(应用双写/影子库)近零最高(两套完整环境 + 应用改造)极高最好对停机零容忍且逻辑复制不可用的极端场景

关于「流复制 + 切换」的适用边界:物理流复制要求主备同大版本,因此它不能把 PG16 升到 PG17。它作为升级手段只在两种变形中成立——其一,升级过程中先拆除复制关系,对主库执行 pg_upgrade 后重新搭建新备库(此时流复制只是「升级后恢复高可用」的手段);其二,借助支持槽同步/快照的工具在两个大版本之间做物理级迁移(非原生能力,依赖第三方)。规划时不要把「主备切换」误当作跨大版本升级的捷径。

关于双跑:两套环境同时在线,应用层把写流量同时发给新旧库(或先切读后切写),观察稳定后下线旧库。它对停机最不敏感,但要求应用配合改造,且两库数据一致性的验证本身就是一项工程,一般只在特殊合规/规模场景下使用。

逻辑复制方案的特殊地位 ​

逻辑复制是唯一「原生支持跨大版本」的在线迁移通道:发布端 PG16、订阅端 PG17 完全合法。它的完整方法论(发布、订阅、限制、切换时的序列处理)已在 逻辑复制原理 一页展开,升级场景的特别提醒有三条:

  • DDL 不复制:升级窗口内新旧库的结构必须由变更流程双端同步;
  • 序列不复制:切换前要手工把新库的序列值 setval 到旧库当前值之上;
  • 大对象不复制:含 lo 的库要么改造存储,要么该部分走 pg_dump。

语法与用法 ​

本节把「升级前检查」需要用到的命令与 SQL 集中列出。无论最终选哪种方案,这些检查都是通用的。

摸底:当前版本与扩展清单 ​

bash
# 服务器与客户端版本
psql -c "SELECT version();"
psql --version
text
                                                 version
----------------------------------------------------------------------------------------------------------
 PostgreSQL 16.4 (Ubuntu 16.4-1.pgdg22.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0) 11.4.0
(1 row)
sql
-- 已安装扩展及版本:大版本升级后扩展二进制/SQL 需要重新对齐
SELECT e.extname,
       e.extversion AS installed,
       v.version    AS available
FROM pg_extension e
JOIN pg_available_extension_versions v
  ON v.name = e.extname
ORDER BY e.extname;

结构预检:pg_dumpall --schema-only ​

把旧库的结构导出成 SQL,在新版本实例上试装(导入到临时库或用解析工具检查),是最可靠的兼容性预检:

bash
# 只导出结构(含全局对象角色与表空间,用 -g 单独处理)
pg_dumpall --schema-only -f schema_check.sql
pg_dumpall --globals-only -f globals_check.sql

# 在新版本实例上试执行(导入临时库)
psql -h 新实例 -p 5433 -d postgres -f globals_check.sql
createdb -h 新实例 -p 5433 schema_test
psql -h 新实例 -p 5433 -d schema_test -f schema_check.sql

新版本对旧语法、已废弃特性的任何不满都会在这一步暴露(如某些类型/函数行为变化、保留字冲突),此时修复成本几乎为零;拖到升级当天才发现,就要在窗口里救火。

pg_upgrade 的 dry-run:--check ​

选定 pg_upgrade 方案后,正式执行前必须跑 --check 模式(参数详解见下一页):

bash
pg_upgrade \
  -b /usr/lib/postgresql/16/bin -B /usr/lib/postgresql/17/bin \
  -d /var/lib/postgresql/16/main -D /var/lib/postgresql/17/main \
  -c
text
Performing Consistency Checks
-----------------------------
Checking cluster versions                                   ok
Checking database user is the install user                  ok
Checking database connection settings                       ok
Checking for prepared transactions                          ok
Checking for system-defined composite types in user tables  ok
Checking for reg* system OID user data types                ok
Checking for contrib/isn with bigint-passing mismatch       ok
Checking for invalid "bool"->"interval" casts               ok
*Clusters are compatible*

插件与扩展的版本对齐 ​

扩展是升级风险最集中的地方:.so 二进制来自操作系统包(按大版本编译),控制文件声明了版本,库里 pg_extension.extversion 记录安装版本。升级前逐个核对:

bash
# 操作系统包里,扩展是否提供了新版本编译产物
apt list --installed 2>/dev/null | grep postgresql
ls /usr/lib/postgresql/17/lib/ | head -30
sql
-- 扩展定义在新版本中可用的最高版本
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
ORDER BY name;

升级完成后,逐个执行 ALTER EXTENSION ... UPDATE 把扩展内部对象对齐到新版本的扩展 SQL(细节见下一页实战)。

备份基线 ​

无论哪种方案,动手前的最后一步永远是一套可直接恢复的备份:

bash
# 逻辑全量(兜底,结构与数据双保险)
pg_dumpall -f full_pre_upgrade.sql

# 或物理备份基线(配合 PITR,细节见物理备份章节)
pg_basebackup -h 127.0.0.1 -U repl -D /backup/pre_upgrade_$(date +%F) -X stream -P

pg_upgrade 方案对旧数据目录的保护机制是「只读不写、保留原样」,但「保留原样」的前提是你没有在失败后误操作它——一份独立备份是最后的后悔药。

详细说明 ​

方案选择决策树 ​

把选型过程压缩成几问:

text
 业务能接受多长的只读/不可用窗口?
 │
 ├─ 零容忍,且库含逻辑复制搞不定的对象(大对象等)
 │    → 双跑(或改造存储后再选逻辑复制)
 │
 ├─ 只能接受秒级~分钟级,但要求全过程业务可写
 │    → 逻辑复制迁移(近零停机,切换秒级)
 │
 ├─ 能接受分钟级停机窗口
 │    → 数据量 < 几百 GB 且窗口够长 → pg_upgrade 拷贝模式 / pg_dumpall
 │    → 数据量大、磁盘紧张             → pg_upgrade --link 模式
 │
 └─ 测试/开发环境 → pg_dumpall 重建(最简单,顺带清历史包袱)

三个补充判断:库中大对象占比高则逻辑复制出局(官方不支持);应用依赖会话级特性(如 session GUC、某些连接状态)则逻辑复制的切换验证要多一轮;磁盘紧张时 pg_upgrade 的拷贝模式与 pg_dumpall 都要按「双倍数据量」预留空间,link 模式只需增量。

pg_upgrade 两种模式的资源账 ​

pg_upgrade 默认把旧数据文件拷贝进新集群目录(原目录保留完整数据),--link 模式则用硬链接把新旧文件关联起来——同一份磁盘块被两个目录项引用,几乎零拷贝:

模式磁盘需求耗时旧目录状态风险点
拷贝(默认)旧 + 新各一份随数据量线性完整独立空间不足导致中途失败
--link几乎无新增显著缩短升级后旧目录的文件与新集群共享 inode升级后对旧目录的任何写操作都会破坏新集群数据,必须严格只读封存

link 模式速度优势明显,但代价是旧目录不再是一份可用的独立回滚副本(文件已与新集群纠缠)。选择 link 就等于把「回滚靠旧目录」这张牌打出去一半——备份基线的优先级因此更高。

升级窗口的时间预算 ​

一次 pg_upgrade 窗口的典型构成:

text
 前置(可在窗口外完成)
 ├── 新版本二进制安装、initdb 新集群、配置对齐     → 窗口外
 ├── pg_upgrade --check 反复演练                   → 窗口外(用克隆库)
 ├── 最后一次备份基线                              → 窗口外/窗口初
 窗口内(硬时间)
 ├── 停应用 → 停库(确认优雅关闭)                 → 1~5 分钟
 ├── pg_upgrade 正式执行(拷贝模式按数据量计)      → 数分钟 ~ 数小时
 ├── vacuumdb --all --analyze-in-stages            → 数分钟(可延后完成)
 ├── 启动新集群 → 冒烟验证 → 拉起应用              → 5~15 分钟
 窗口外
 ├── 重建备库(pg_basebackup)                     → 数小时,可后置
 ├── ALTER EXTENSION UPDATE、统计补齐              → 后置
 └── 观察期(慢查询、错误日志)                    → 3~7 天

要点:pg_upgrade 本体执行时间的最大变量是表数量与文件数量(每个文件都要建目录项/硬链接),而不只是数据体积;-j 并行能显著加速多表场景。

回滚方案的设计 ​

pg_upgrade 不改写旧集群,因此「回滚」有明确的定义:放弃新集群,把应用指回旧集群。可靠的回滚设计包含四件事:

  1. 窗口内任何时刻都可回:正式 pg_upgrade 前,新旧两套目录、两套二进制都在,应用连接串准备好「指回旧库」的版本。
  2. 切换后设观察期门禁:切换后旧目录封存只读(不要删除!),观察期内发现严重问题仍可回切(代价是丢失新集群上产生的写入)。
  3. 回滚的判定标准提前写死:哪些问题触发回滚(数据校验不一致、核心功能异常、性能塌方),哪些问题允许带病运行(个别非核心扩展版本偏差)。临场争论是窗口里最贵的动作。
  4. 高可用架构的回滚联动:若升级期间拆了备库,回滚时旧主库即回滚目标;新备库重建动作要等回滚判定后才启动,避免来回拉扯。

风险清单:大版本升级的已知雷区 ​

风险说明缓解
扩展不兼容扩展未提供新大版本产物,或行为变化升级前核对 pg_available_extensions 与包管理器产物;无解的扩展推迟升级
认证/协议变化新版本移除旧认证方式(如多年来的 md5 淘汰趋势)预先把密码认证迁移到 SCRAM
优化器行为变化新版本计划选择不同,个别查询变慢预演环境跑回归集;pg_upgrade 后及时 analyze;保留 pg_stat_statements 基线对比
系统目录/保留字冲突应用表/列名与新版本新保留字冲突--check 与 schema 试装提前暴露
prepared transactions 遗留升级时存在未决两阶段事务会被拒绝--check 会报;升级前 pg_prepared_xacts 清零
序列与复制标识遗漏逻辑复制方案切换时序列值不同步切换检查单强制 setval 对齐
备库重建窗口主库升级完成后,高可用处于降级状态把备库重建排进升级总计划并监控其完成

升级与巡检、监控的衔接 ​

把升级当作一次「运维体系大考」:升级前后的巡检报告对比(表膨胀、索引密度、连接水位、WAL 速率)是验证「升级没有引入隐性回归」的最佳证据。建议在升级完成一周后做一次专项巡检,对比三个维度:性能(pg_stat_statements 平均耗时)、体积(数据目录大小变化属于正常现象——新集群默认布局可能有差异)、错误日志(FATAL/ERROR 的类型分布)。

pg_dumpall 方案详解 ​

pg_dumpall 是「重建式升级」的代表:把整个实例(或逐库用 pg_dump)导出为 SQL/COPY 文本,再在新版本实例上重放。它的全部细节(格式选择、并行恢复、全局对象)在 pg_dumpall 逻辑备份 一页展开,升级视角下的核心账目:

bash
# 导出:全局对象 + 每库数据
pg_dumpall --globals-only -f globals.sql
pg_dump -Fc -Z 1 -f appdb.dump appdb          # 目录格式 + 压缩,恢复可并行

# 导入:先全局对象,再逐库
psql -h new17 -f globals.sql
createdb -h new17 appdb
pg_restore -h new17 -d appdb -j 8 appdb.dump  # -j 并行恢复
维度评估
停机导出 + 导入全程数据冻结,小时级起步且随规模线性
磁盘导出文件 ≈ 数据量 ×(压缩率)+ 新库双份
索引与统计恢复时重建所有索引(这是耗时大头),统计重建
跨平台无限制(文本/逻辑格式),跨架构、跨云均可用
附带收益顺带消除膨胀(全新页面)、可跳过废弃对象、可整理结构

选它的场景:库不大(几十 GB 以内)、需要跨平台迁移、或明确要借升级「洗一遍」数据(去膨胀、清垃圾对象)。大库用它只会在恢复阶段的索引重建上耗尽窗口耐心。

双跑方案的实施要点 ​

双跑(blue-green)把「停机」问题转化为「一致性验证」问题,实施骨架:

text
 阶段 0  新环境就绪(新版本集群 + 应用兼容性改造)
 阶段 1  全量初始化(逻辑复制 copy_data 或备份恢复)
 阶段 2  持续追平(逻辑复制增量,监控延迟趋近于零)
 阶段 3  灰度切读(只读流量逐步指向新库,写仍走旧库)
 阶段 4  短暂停写窗口(秒级):序列对齐 → 切写 → 新库为主
 阶段 5  反向保护(旧库保留只读封存 + 观察期)
 阶段 6  下线旧库

它对停机最不敏感,但三个隐性成本要提前算:应用改造(双写开关或数据源路由)、一致性验证(切写前对新旧库做关键表 checksum/计数比对)、回切路径(阶段 4 之后若发现问题,旧库已缺增量,需要反向同步或接受丢数)。没有应用团队深度配合的部署,双跑方案基本不可行。

停机窗口的量化:RTO 与数据搬运速率 ​

把「窗口够不够」从感觉变成算术。三个速率是全部估算的地基:

操作经验速率(机械盘/SATA SSD/NVMe 的量级差异很大,务必实测)
pg_dump 导出100~400 MB/s(受压缩与 CPU 影响)
pg_restore 导入50~200 MB/s(索引重建是瓶颈)
pg_upgrade 拷贝模式200~800 MB/s(纯文件拷贝)
pg_upgrade link 模式只建目录项,分钟级恒定(与数据量基本无关)
逻辑复制初始同步受发布端读 I/O 与网络限制,常见 50~200 MB/s

例:500 GB 库、NVMe 环境——拷贝模式 pg_upgrade 约 20~40 分钟(含检查与收尾);pg_dumpall 约 1.5~3 小时;逻辑复制初始同步 1~2 小时(窗口外完成)+ 秒级切换。用演练实测校准,再按 1.5 倍冗余申报窗口——申报 40 分钟实际跑了 70 分钟,是变更管理上的事故,哪怕技术上是成功的。

升级时间线与责任分工 ​

大版本升级是跨角色协作,时间线上每个阶段都有明确的 owner:

阶段内容Owner前置
T-30 天方案选型评审、停机预算签字DBA + 应用负责人—
T-21 天预检(结构试装、扩展核对)+ 演练环境搭建DBA选型
T-14 天演练两轮(含回滚),产出 runbookDBA预检
T-7 天应用回归测试(新版本 + 新实例)应用团队runbook
T-3 天变更评审会:窗口、人员、回滚判定标准全体回归通过
T-0正式执行 + 观察期启动DBA 值班 + 应用值班评审
T+3 天观察期复盘,专项对比巡检DBA观察
T+7 天关闭变更单,旧目录清理评审DBA复盘

把这张表放进变更单,缺任何一个「前置」都自动顺延——升级事故的大多数根因不是技术失败,而是前置步骤被时间压力挤掉。

扩展兼容性的分层检查 ​

扩展是升级风险最集中的组件,预检要分四层确认,任何一层断了都要先解决再排期:

text
 第 1 层:二进制产物   新大版本的 .so 是否存在于 OS 包
                        ls /usr/lib/postgresql/17/lib/<ext>.so
 第 2 层:扩展定义     pg_available_extensions 是否列出该扩展及新 default_version
 第 3 层:升级路径     pg_available_extension_versions 中是否存在从当前版本到新版本的 update path
 第 4 层:行为兼容     扩展 release notes 中是否有破坏性变更(如函数签名变化)
sql
-- 第 2、3 层的一体化检查
SELECT name,
       version,
       default_version,
       installed_version
FROM pg_available_extension_versions v
JOIN pg_extension e ON e.extname = v.name
ORDER BY name, version;
text
        name        | version | default_version | installed_version
--------------------+---------+-----------------+-------------------
 pg_stat_statements | 1.10    | 1.10            | 1.7
 pg_trgm            | 1.6     | 1.6             | 1.4
 postgis            | 3.4.2   | 3.4.2           | 3.3.2

installed_version 落后于 default_version 属于正常(升级后用 ALTER EXTENSION UPDATE 对齐);若新版本列表里根本查不到某个已安装扩展(第 1、2 层断链),说明 OS 包或仓库缺失该扩展的新版本产物——此时要么补装,要么把该扩展的替代方案纳入升级前置工作。把这张检查表逐库跑一遍并归档,是扩展风险的最小闭环。

实战示例 ​

示例一:升级前检查清单的脚本化 ​

把预检做成脚本,产出可以直接贴进变更单的检查结果:

bash
#!/usr/bin/env bash
# pre_upgrade_check.sh —— 大版本升级前置检查
set -u
PGBIN_OLD="/usr/lib/postgresql/16/bin"

echo "=== 1. 版本信息 ==="
psql -Atc "SELECT version();"

echo "=== 2. 扩展安装版本 vs 可用版本 ==="
psql -c "
SELECT e.extname, e.extversion AS installed, a.default_version AS available
FROM pg_extension e JOIN pg_available_extensions a ON a.name = e.extname
ORDER BY 1;"

echo "=== 3. 未决两阶段事务(必须为空) ==="
psql -c "SELECT gid, prepared FROM pg_prepared_xacts;"

echo "=== 4. 大对象数量(逻辑复制方案需评估) ==="
psql -Atc "SELECT count(*) FROM pg_largeobject_metadata;"

echo "=== 5. 长连接/预备语句状态(升级前应排空) ==="
psql -c "
SELECT count(*) FILTER (WHERE backend_type='client backend') AS clients,
       count(*) FILTER (WHERE xact_start IS NOT NULL)        AS in_xact
FROM pg_stat_activity;"

echo "=== 6. 复制拓扑(升级期间需要拆除/重建的对象) ==="
psql -c "SELECT slot_name, slot_type, active FROM pg_replication_slots;"
psql -c "SELECT application_name, state FROM pg_stat_replication;"

echo "=== 7. 数据规模概览 ==="
psql -c "
SELECT pg_size_pretty(sum(pg_database_size(datname))) AS total
FROM pg_database WHERE datallowconn;"

示例二:逻辑复制升级的切换演练要点 ​

以 PG16 → PG17 为例,方案骨架(细节参考逻辑复制章节):

sql
-- 新库(17):建好同构表后创建订阅
CREATE SUBSCRIPTION sub_upgrade
    CONNECTION 'host=old16 dbname=app user=repl password=...'
    PUBLICATION pub_upgrade
    WITH (copy_data = true, streaming = on);
bash
# 初始同步完成后,观察追平
psql -h new17 -c "
SELECT subname, latest_end_lsn,
       pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), latest_end_lsn)) AS lag
FROM pg_stat_subscription;"   # 在 16 上观察槽的消费进度
sql
-- 切换窗口(秒级):
-- 1. 应用停写(只读模式或暂停队列)
-- 2. 确认订阅端完全追平(16 上槽的 retained 接近 0)
-- 3. 对齐序列(逻辑复制不复制序列!)
sql
-- 在旧库批量生成序列对齐语句
SELECT format('SELECT setval(%L, %s);',
              quote_ident(s.schemaname) || '.' || quote_ident(s.sequencename),
              s.last_value)
FROM pg_sequences s
WHERE s.last_value IS NOT NULL;
sql
-- 4. 新库执行上一步的 setval 输出,然后应用切到新库

演练环境完整跑一遍(含回切),把每一步的耗时与人工动作记录成 runbook。

示例三:小版本滚动升级 ​

bash
# 1. 备库先升级(数据兼容,直接换二进制)
ssh standby-host
apt install postgresql-16=16.4-1.pgdg22.04+1   # 示意:升级补丁版本
pg_ctlcluster 16 main restart

# 2. 观察备库健康,验证复制正常
psql -h 主库 -c "SELECT application_name, state, replay_lag FROM pg_stat_replication;"

# 3. 主备切换(switchover),原主库变备库
# 4. 升级原主库的二进制并重启,恢复复制

整个过程中业务最多经历一次秒级切换,高可用始终在线。小版本的安全修复应尽快按此流程跟进。

示例四:升级窗口的验证脚本 ​

切换完成后的冒烟验证固定跑一遍,避免「升级成功但业务跑不起来」:

bash
#!/usr/bin/env bash
# smoke_test.sh —— 升级后冒烟验证
echo "=== 版本确认 ==="
psql -Atc "SHOW server_version;"

echo "=== 核心表可读 + 行数抽样 ==="
psql -c "
SELECT 'orders'  AS t, count(*) FROM public.orders
UNION ALL SELECT 'users', count(*) FROM public.users;"

echo "=== 写入探测(事务内回滚,不留数据) ==="
psql -c "
BEGIN;
INSERT INTO public.smoke_probe VALUES (1, now());
ROLLBACK;"

echo "=== 扩展可用性 ==="
psql -c "SELECT extname, extversion FROM pg_extension ORDER BY 1;"

echo "=== 复制恢复确认(若已重建备库) ==="
psql -c "SELECT application_name, state FROM pg_stat_replication;"

echo "=== 连接与活跃会话 ==="
psql -c "SELECT state, count(*) FROM pg_stat_activity
         WHERE backend_type='client backend' GROUP BY 1;"

示例三:pg_dumpall 重建一个 30 GB 的测试库 ​

测试环境从 PG14 → PG17,数据 30 GB,允许停机半天,顺带清理多年积累的废弃表。选 pg_dumpall 方案:

bash
# 1. 新实例初始化(PG17)
/usr/lib/postgresql/17/bin/initdb -D /data/pg17-test --locale=C.UTF-8
/usr/lib/postgresql/17/bin/pg_ctl -D /data/pg17-test -l /tmp/pg17.log start

# 2. 导出全局对象与数据(旧实例 PG14 仍在服务测试流量,先只读)
psql -c "ALTER DATABASE appdb SET default_transaction_read_only = on;"
pg_dumpall --globals-only -h old14 -f globals.sql
pg_dump -Fc -Z 1 -h old14 -f appdb.dump appdb

# 3. 导入
psql -h localhost -p 新端口 -f globals.sql
createdb -h localhost -p 新端口 appdb
pg_restore -h localhost -p 新端口 -d appdb -j 8 appdb.dump
text
...(恢复输出节选)
pg_restore: creating TABLE "public.orders"
pg_restore: creating TABLE DATA "public.orders"
pg_restore: creating INDEX "public.orders_pkey"
...
duration: 52 min 18 s
bash
# 4. 清理:恢复后在暂存库比对废弃表,确认后丢弃(重建方案的红利)
psql -d appdb -c "\dt legacy_*"        # 列出遗留表,逐个评审

# 5. 统计与校验
psql -d appdb -c "ANALYZE;"
psql -d appdb -c "SELECT count(*) FROM orders;"   # 与旧库基准对比

# 6. 解除只读、指向新实例
psql -d appdb -c "ALTER DATABASE appdb SET default_transaction_read_only = off;"
# 应用连接串切到 PG17 实例
text
  count
----------
 18204431
(1 row)

30 GB 的库全程约 1 小时(导出 18 分钟 + 恢复 52 分钟并行压缩后),附带收益:新库零膨胀、废弃表有了清单、统计全新。这就是「小库用重建」的完整闭环。

示例四:升级时间线的核对执行 ​

按「升级时间线与责任分工」表的 T-21 天检查项,产出核对记录:

text
 [x] 结构试装:schema_check.sql 在 PG17 临时实例导入成功(2 处保留字冲突已整改)
 [x] 扩展分层检查:4/6 扩展全通过;postgis 需升级 OS 包到 3.4.2(已建单)
 [x] 演练环境就绪(生产快照恢复,与生产同规格磁盘)
 [ ] 演练第一轮(link 模式) —— 排期周四
 [ ] 演练第二轮(含回滚与冒烟) —— 排期下周一
 [ ] 应用回归 —— 依赖演练完成

升级管理的一半工作量在核对与推进,把这张核对单贴在变更单顶部,任何阶段卡住都一目了然。

注意事项 ​

不要把「主备切换」当成跨大版本升级手段。 流复制跨大版本不兼容,无论切换多少次,两边还是各自的旧版本。跨大版本只有「搬数据」一条路(本页四种搬法)。

扩展是升级失败的第一大原因。 操作系统包里扩展的 .so 按大版本分别编译(/usr/lib/postgresql/16/lib/ 与 17/lib/),升级前必须确认所有在用扩展在目标版本有对应产物,且扩展的默认版本与库内安装版本的升级路径(ALTER EXTENSION UPDATE)存在。

升级前排空准备事务与空闲事务。 pg_upgrade 与逻辑复制的初始同步都会被未决事务干扰;执行前用巡检的手段(长事务检查)确认干净。

pg_dumpall 方案不要漏掉全局对象。 角色与表空间在 pg_dumpall 的 globals 部分;只 dump 数据库不 dump globals,恢复后会发现权限全丢。

逻辑复制方案的序列对齐是遗漏率最高的一步。 序列不复制,切换后自增主键立即冲突或主键重复;切换检查单里必须显式包含「所有序列 setval 对齐」(见示例二)。

观察期内旧数据目录只读封存。 pg_upgrade link 模式下新旧文件共享 inode,对旧目录的任何写操作(包括误启动旧实例)都可能破坏新集群数据;至少在观察期结束前把旧目录设置为只读并停止对应服务。

升级后的统计信息从零开始。 pg_upgrade 不搬统计信息(pg_statistic),新集群的查询计划器最初是「盲人」;vacuumdb --all --analyze-in-stages(较新版本中替代了旧的 analyze_new_cluster.sh 脚本)要尽早执行,且业务高峰前再补一轮完整 ANALYZE。

计划器行为变化要靠预演暴露。 大版本之间优化器与执行器的改进可能改变个别查询的计划;升级前在克隆环境用真实负载回归,比上线后救火便宜一百倍。

不要在升级窗口里夹带其他变更。 升级当天只做升级——同时改 OS 内核参数、换连接池版本、调整磁盘布局,会把一次本可定位的失败变成多方互相指认的罗生门。升级窗口的变更隔离原则与任何高危变更一致。

演练环境必须与生产「同构」到关键维度。 数据规模(至少最大表的真实样本)、磁盘类型、版本组合、扩展清单、网络拓扑对演练结论的有效性至关重要;拿一个 2 GB 的开发库演练 800 GB 的生产升级,演练结果没有参考价值。

最佳实践 ​

  1. 先定停机预算,再选方案。 停机预算(业务方签字确认的分钟数)是唯一能排除大多数选项的硬约束,其余维度都只是打分。
  2. 用 pg_dumpall --schema-only 做结构试装。 十分钟的预检能消掉 80% 的窗口内兼容性问题,成本收益比最高的升级动作。
  3. 所有方案都先在克隆环境完整演练一遍,含回滚。 演练产出 runbook(每步命令、耗时、验证点),正式窗口照本宣科。
  4. pg_upgrade 优先评估 --link 模式,但先算清楚磁盘账与回滚账。 数据量大、磁盘紧张时 link 是唯一可行解;选它就必须接受「回滚不再依赖旧目录」的前提,备份基线权重上调。
  5. 大版本升级排进年度变更日历,并捆绑小版本跟进机制。 升级当日距最新小版本可能已隔数月,升级时直接上目标大版本的最新小版本。
  6. 升级前完成 SCRAM 等认证迁移。 新版本逐步淘汰旧认证方式,升级当天同时改认证是最容易翻车的组合,提前分步做。
  7. 升级后立即 vacuumdb --all --analyze-in-stages,高峰前再完整 ANALYZE。 统计缺失期的计划劣化是大版本升级最常见的次生故障。
  8. 逐个执行 ALTER EXTENSION ... UPDATE。 新集群里扩展处于旧定义,未对齐的扩展在遇到新版本功能或修复时行为不明。
  9. 升级完成一周后做专项对比巡检。 用巡检一节的快照体系对比升级前后:pg_stat_statements 耗时、表大小、错误日志分布,让「升级没副作用」成为可证明的结论。
  10. 把备库重建纳入升级总时间线。 主库升级完成 ≠ 升级完成;高可用降级状态下集群是脆弱的,备库重建(pg_basebackup)与验证结束才算关闭变更单。
  11. 每次升级沉淀一份「风险清单修订」。 本页的雷区表加上本次实际踩到的坑,成为团队资产;下一次升级的评审会上先过一遍。

下一节

继续学习:pg_upgrade 实战

参考链接 ​