版本升级概述
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 记录格式、复制协议消息。大版本发布时这些内部结构允许变化,于是旧版本写下的数据文件,新版本无法按旧语义读取。所有大版本升级方案,无论包装成什么名字,本质都是四种「搬法」之一:
① 元数据重建 + 文件复用 → 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 集中列出。无论最终选哪种方案,这些检查都是通用的。
摸底:当前版本与扩展清单
# 服务器与客户端版本
psql -c "SELECT version();"
psql --version 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 需要重新对齐
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,在新版本实例上试装(导入到临时库或用解析工具检查),是最可靠的兼容性预检:
# 只导出结构(含全局对象角色与表空间,用 -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 模式(参数详解见下一页):
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 \
-cPerforming 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 记录安装版本。升级前逐个核对:
# 操作系统包里,扩展是否提供了新版本编译产物
apt list --installed 2>/dev/null | grep postgresql
ls /usr/lib/postgresql/17/lib/ | head -30-- 扩展定义在新版本中可用的最高版本
SELECT name, default_version, installed_version
FROM pg_available_extensions
WHERE installed_version IS NOT NULL
ORDER BY name;升级完成后,逐个执行 ALTER EXTENSION ... UPDATE 把扩展内部对象对齐到新版本的扩展 SQL(细节见下一页实战)。
备份基线
无论哪种方案,动手前的最后一步永远是一套可直接恢复的备份:
# 逻辑全量(兜底,结构与数据双保险)
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 -Ppg_upgrade 方案对旧数据目录的保护机制是「只读不写、保留原样」,但「保留原样」的前提是你没有在失败后误操作它——一份独立备份是最后的后悔药。
详细说明
方案选择决策树
把选型过程压缩成几问:
业务能接受多长的只读/不可用窗口?
│
├─ 零容忍,且库含逻辑复制搞不定的对象(大对象等)
│ → 双跑(或改造存储后再选逻辑复制)
│
├─ 只能接受秒级~分钟级,但要求全过程业务可写
│ → 逻辑复制迁移(近零停机,切换秒级)
│
├─ 能接受分钟级停机窗口
│ → 数据量 < 几百 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 窗口的典型构成:
前置(可在窗口外完成)
├── 新版本二进制安装、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 不改写旧集群,因此「回滚」有明确的定义:放弃新集群,把应用指回旧集群。可靠的回滚设计包含四件事:
- 窗口内任何时刻都可回:正式 pg_upgrade 前,新旧两套目录、两套二进制都在,应用连接串准备好「指回旧库」的版本。
- 切换后设观察期门禁:切换后旧目录封存只读(不要删除!),观察期内发现严重问题仍可回切(代价是丢失新集群上产生的写入)。
- 回滚的判定标准提前写死:哪些问题触发回滚(数据校验不一致、核心功能异常、性能塌方),哪些问题允许带病运行(个别非核心扩展版本偏差)。临场争论是窗口里最贵的动作。
- 高可用架构的回滚联动:若升级期间拆了备库,回滚时旧主库即回滚目标;新备库重建动作要等回滚判定后才启动,避免来回拉扯。
风险清单:大版本升级的已知雷区
| 风险 | 说明 | 缓解 |
|---|---|---|
| 扩展不兼容 | 扩展未提供新大版本产物,或行为变化 | 升级前核对 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 逻辑备份 一页展开,升级视角下的核心账目:
# 导出:全局对象 + 每库数据
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)把「停机」问题转化为「一致性验证」问题,实施骨架:
阶段 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 天 | 演练两轮(含回滚),产出 runbook | DBA | 预检 |
| T-7 天 | 应用回归测试(新版本 + 新实例) | 应用团队 | runbook |
| T-3 天 | 变更评审会:窗口、人员、回滚判定标准 | 全体 | 回归通过 |
| T-0 | 正式执行 + 观察期启动 | DBA 值班 + 应用值班 | 评审 |
| T+3 天 | 观察期复盘,专项对比巡检 | DBA | 观察 |
| T+7 天 | 关闭变更单,旧目录清理评审 | DBA | 复盘 |
把这张表放进变更单,缺任何一个「前置」都自动顺延——升级事故的大多数根因不是技术失败,而是前置步骤被时间压力挤掉。
扩展兼容性的分层检查
扩展是升级风险最集中的组件,预检要分四层确认,任何一层断了都要先解决再排期:
第 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 中是否有破坏性变更(如函数签名变化)-- 第 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; 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.2installed_version 落后于 default_version 属于正常(升级后用 ALTER EXTENSION UPDATE 对齐);若新版本列表里根本查不到某个已安装扩展(第 1、2 层断链),说明 OS 包或仓库缺失该扩展的新版本产物——此时要么补装,要么把该扩展的替代方案纳入升级前置工作。把这张检查表逐库跑一遍并归档,是扩展风险的最小闭环。
实战示例
示例一:升级前检查清单的脚本化
把预检做成脚本,产出可以直接贴进变更单的检查结果:
#!/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 为例,方案骨架(细节参考逻辑复制章节):
-- 新库(17):建好同构表后创建订阅
CREATE SUBSCRIPTION sub_upgrade
CONNECTION 'host=old16 dbname=app user=repl password=...'
PUBLICATION pub_upgrade
WITH (copy_data = true, streaming = on);# 初始同步完成后,观察追平
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 上观察槽的消费进度-- 切换窗口(秒级):
-- 1. 应用停写(只读模式或暂停队列)
-- 2. 确认订阅端完全追平(16 上槽的 retained 接近 0)
-- 3. 对齐序列(逻辑复制不复制序列!)-- 在旧库批量生成序列对齐语句
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;-- 4. 新库执行上一步的 setval 输出,然后应用切到新库演练环境完整跑一遍(含回切),把每一步的耗时与人工动作记录成 runbook。
示例三:小版本滚动升级
# 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. 升级原主库的二进制并重启,恢复复制整个过程中业务最多经历一次秒级切换,高可用始终在线。小版本的安全修复应尽快按此流程跟进。
示例四:升级窗口的验证脚本
切换完成后的冒烟验证固定跑一遍,避免「升级成功但业务跑不起来」:
#!/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 方案:
# 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...(恢复输出节选)
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# 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 实例 count
----------
18204431
(1 row)30 GB 的库全程约 1 小时(导出 18 分钟 + 恢复 52 分钟并行压缩后),附带收益:新库零膨胀、废弃表有了清单、统计全新。这就是「小库用重建」的完整闭环。
示例四:升级时间线的核对执行
按「升级时间线与责任分工」表的 T-21 天检查项,产出核对记录:
[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 的生产升级,演练结果没有参考价值。
最佳实践
- 先定停机预算,再选方案。 停机预算(业务方签字确认的分钟数)是唯一能排除大多数选项的硬约束,其余维度都只是打分。
- 用
pg_dumpall --schema-only做结构试装。 十分钟的预检能消掉 80% 的窗口内兼容性问题,成本收益比最高的升级动作。 - 所有方案都先在克隆环境完整演练一遍,含回滚。 演练产出 runbook(每步命令、耗时、验证点),正式窗口照本宣科。
- pg_upgrade 优先评估
--link模式,但先算清楚磁盘账与回滚账。 数据量大、磁盘紧张时 link 是唯一可行解;选它就必须接受「回滚不再依赖旧目录」的前提,备份基线权重上调。 - 大版本升级排进年度变更日历,并捆绑小版本跟进机制。 升级当日距最新小版本可能已隔数月,升级时直接上目标大版本的最新小版本。
- 升级前完成 SCRAM 等认证迁移。 新版本逐步淘汰旧认证方式,升级当天同时改认证是最容易翻车的组合,提前分步做。
- 升级后立即
vacuumdb --all --analyze-in-stages,高峰前再完整 ANALYZE。 统计缺失期的计划劣化是大版本升级最常见的次生故障。 - 逐个执行
ALTER EXTENSION ... UPDATE。 新集群里扩展处于旧定义,未对齐的扩展在遇到新版本功能或修复时行为不明。 - 升级完成一周后做专项对比巡检。 用巡检一节的快照体系对比升级前后:
pg_stat_statements耗时、表大小、错误日志分布,让「升级没副作用」成为可证明的结论。 - 把备库重建纳入升级总时间线。 主库升级完成 ≠ 升级完成;高可用降级状态下集群是脆弱的,备库重建(pg_basebackup)与验证结束才算关闭变更单。
- 每次升级沉淀一份「风险清单修订」。 本页的雷区表加上本次实际踩到的坑,成为团队资产;下一次升级的评审会上先过一遍。
下一节
继续学习:pg_upgrade 实战
参考链接
- PostgreSQL 官方文档:Version and Platform Compatibility / Upgrading
- PostgreSQL 官方文档:pg_upgrade
- PostgreSQL 官方文档:pg_dumpall
- PostgreSQL 官方文档:逻辑复制(跨版本迁移)
- PostgreSQL 官方文档:Supported Platforms 与版本策略
- PostgreSQL 官方:Versioning Policy(版本号与支持策略)
- PostgreSQL 官方文档:pg_available_extensions 视图
- PostgreSQL 官方文档:pg_extension 目录
- PostgreSQL 官方文档:vacuumdb
- PostgreSQL Wiki:Upgrading a PostgreSQL Cluster