PostgreSQL大版本升级如果沿用pg_dump导出再导入的方式,数据量一旦上去,停机时间会变得很难控制。pg_upgrade提供的是原地升级思路,它直接复用旧版本的数据文件,把系统表转换成新版本格式,因此主流程不涉及重建索引和逐行插入数据。本文围绕Linux环境下使用pg_upgrade从旧版本升级到新版本的完整过程展开,覆盖工作原理、升级前检查、命令执行、升级后验证以及回退与排错。

一、pg_upgrade到底做了什么
要理解升级步骤,先得清楚pg_upgrade与逻辑导出导入的本质区别。pg_dump导出的是一系列SQL文本,恢复时数据库需要重新建表、恢复主外键、创建索引并逐行写入数据;而pg_upgrade直接操作PostgreSQL的数据目录,把旧版本集群中的用户表物理文件、系统表结构以及相关元数据转换成新版本能够识别的格式。对于数据量很大的库,重建索引和约束通常比复制文件慢得多,所以pg_upgrade的优势非常明显。
pg_upgrade启动后会分别连接旧集群和新集群,读取pg_catalog中的元数据,把旧版本系统表结构映射到新版本结构,同时保留用户表的物理文件。它不会重新排序或重写堆表数据,因此理想情况下,整个升级时长主要取决于系统表转换、数据文件复制或硬链接创建,以及最后的一致性检查。
不过pg_upgrade并不是所有场景都适用。如果旧库中存在新版本已经移除的数据类型、不兼容的扩展版本、checksum设置不一致,或者需要改变字符集和操作系统架构,就可能失败。因此升级前必须使用check参数做一次完整模拟检查,不要直接执行正式升级。
二、升级前的环境准备与兼容性检查
在停止旧集群前,先把新版本软件包装好。以CentOS和RHEL系列为例,可以通过官方仓库安装postgresql16-server和postgresql16-contrib,安装完成后会同时生成/usr/pgsql-16/bin目录以及默认的/var/lib/pgsql/16/data目录。此时不要直接使用旧数据目录,必须先用新版本initdb初始化一个全新的数据目录。
/usr/pgsql-16/bin/initdb -D /var/lib/pgsql/16/data --locale=en_US.UTF-8 --encoding=UTF8
接下来检查新旧集群的编译选项和扩展。运行SELECT version();查看版本,运行SELECT * FROM pg_available_extensions;确认目标版本是否提供旧库使用过的扩展。还可以用pg_config --configure对比编译参数,重点看调试选项、断言选项、块大小和checksum设置。如果旧集群启用了data checksums,新集群也必须启用,否则pg_upgrade会直接报错。
然后停掉旧版本数据库服务,但先不要启动新集群。pg_upgrade需要以操作系统用户postgres运行,且当前工作目录不能位于数据目录内部,否则权限和文件占用可能导致失败。可以使用su - postgres切换到数据库系统用户,并创建专门的工作目录。
systemctl stop postgresql-14 su - postgres mkdir -p /tmp/pg_upgrade_work cd /tmp/pg_upgrade_work
三、执行pg_upgrade的完整命令与参数说明
官方推荐的第一次动作不是正式升级,而是先运行带有check参数的pg_upgrade。它能用目标版本的二进制读取旧数据目录,检查系统表转换、扩展兼容性、权限和磁盘空间,并且不会修改任何数据。很多问题都可以在这个阶段暴露出来,尤其是缺失动态库、不兼容的PL/pgSQL或自定义类型。
/usr/pgsql-16/bin/pg_upgrade \ --old-datadir=/var/lib/pgsql/14/data \ --new-datadir=/var/lib/pgsql/16/data \ --old-bindir=/usr/pgsql-14/bin \ --new-bindir=/usr/pgsql-16/bin \ --check \ --username=postgres
如果check输出中包含兼容性通过的提示,说明可以继续。正式升级命令把check参数替换为实际执行参数。jobs参数可以并行处理多个数据库或表空间,提高大库转换速度;link参数使用硬链接而不是复制文件,几乎不增加磁盘占用,但升级后旧数据目录和新数据目录指向同一inode,一旦写入新库就会影响旧文件,不能再启动旧集群。如果没有开启归档或没有物理备份,建议不要使用link模式。
/usr/pgsql-16/bin/pg_upgrade \ --old-datadir=/var/lib/pgsql/14/data \ --new-datadir=/var/lib/pgsql/16/data \ --old-bindir=/usr/pgsql-14/bin \ --new-bindir=/usr/pgsql-16/bin \ --jobs=4 \ --link \ --username=postgres
命令结束后,屏幕会提示需要运行analyze_new_cluster.sh脚本,或者手动执行analyze。不要忽略这一步,因为升级后统计信息可能仍是旧版本格式或已经过时,规划器可能生成低效执行计划。可以用pg_upgrade生成的脚本处理,也可以手动执行vacuumdb --all --analyze-in-stages。
四、升级后的验证、配置迁移与统计信息收集
正式升级完成后,先把旧集群的postgresql.conf、pg_hba.conf和pg_ident.conf中的有效配置迁移到新数据目录。注意不要直接复制整个文件,因为新版本可能新增或调整了参数,直接覆盖可能丢失默认值。建议用diff对比新旧配置文件,只迁移业务相关的参数,如shared_buffers、max_connections、archive_command等。
然后启动新版本服务,先以postgres用户登录,执行SELECT version();确认版本已经切换,再检查关键表数量和索引状态。下面的SQL可以查看每个用户表是否有失效索引或需要修复的约束。
SELECT schemaname, relname, indexrelname, indisvalid FROM pg_stat_user_indexes WHERE indisvalid = false;
统计信息收集建议使用新版本自带的vacuumdb工具,它支持analyze-in-stages参数,先收集低精度统计信息,再逐步细化,适合升级后快速恢复业务。执行时间与表数量、数据量相关,可以放在业务低谷运行。
/usr/pgsql-16/bin/vacuumdb --all --analyze-in-stages --username=postgres
验证应用连接时,还要注意连接字符串中的端口是否已经从旧版本切换到新版本。如果新旧版本使用不同端口,可以暂时保持旧库停机、新库监听原端口,减少应用改动。确认一切正常后,旧数据目录可以保留一段时间,但不要与新集群同时启动,避免端口和数据文件冲突。
五、回退策略与常见报错定位
升级前必须制定回退方案。使用普通复制模式的pg_upgrade会保留完整的旧数据目录,只要不删除旧目录,就可以在新集群出现问题时停掉新库,重新用旧版本二进制启动旧数据目录。但如果使用link模式,旧目录中的文件已经与新目录共享,此时旧集群已不能安全启动,因此link模式务必配合物理备份或虚拟机快照。
常见错误之一是checksum设置不一致。这表示旧集群开了data checksums而新集群没开,需要在initdb时加上data-checksums参数重新初始化目标数据目录。另一个常见错误是扩展不可用,解决办法是在目标版本安装对应扩展,或者在旧库中先删除无法迁移的扩展。如果遇到动态库缺失,通常是因为新版本缺少contrib包,安装postgresql16-contrib即可解决。
如果check报告中出现扩展未加载的提示,一般需要确认旧库中哪些扩展在目标版本没有预加载。可以在旧库执行SELECT extname, extversion FROM pg_extension ORDER BY extname;对比目标版本的可用扩展列表。对于业务依赖的自定义扩展,需要先用新版本工具链重新编译,再执行pg_upgrade。
总体来看,pg_upgrade是PostgreSQL大版本升级时平衡速度与安全的重要工具。只要提前做好兼容检查、理解link模式的风险、升级后补足统计信息,就能把停机窗口控制到很短。
PostgreSQL版本升级pg_upgrade数据库迁移修改时间:2026-10-03 12:22:22