☰
PostgreSQL 14升级15:Oracle Linux 7.9下pg_upgrade实战全记录
2026/10/14 4:53:34 网站建设 项目流程

最近在 Oracle Linux 7.9 环境里完成了一次 PostgreSQL 14 到 PostgreSQL 15 的升级,整个过程比预想中顺利,也踩了几个不算少见但很容易被忽略的坑。这篇文章就把这次升级的完整路径拆开来讲:从升级方式选型、环境检查、数据备份、PG15 安装部署,到使用官方 pg_upgrade 工具完成数据迁移,再到升级后的配置对齐和问题排查,一条线串下来。

如果你正准备把 PostgreSQL 14 升级到 15,尤其是运行在 Oracle Linux 7.9 或同类 EL7 系统上,这篇记录可以直接拿去参考。文章里的命令和步骤我都按实际操作顺序整理过,不是照着官方文档念一遍,而是结合我自己的执行过程做了筛选和补充。升级这种事,最怕的不是版本差异,而是升级前没想清楚、升级后不知道看哪里。希望你看完能少走几步弯路。

1. 升级前的整体思路与实际选型

1.1 为什么要在这个时间点升级

PostgreSQL 15 相比 14,最让我看中的不是某个炫酷新特性,而是几个务实层面的变化:

第一,逻辑复制功能在 15 里支持了行过滤器,可以把一个大表的同步范围从整表缩到指定条件。对于多套实例之间做数据分发场景来说,这个能力直接改变了一部分数据管道设计。第二,15 的pg_stat_io视图能更细粒度地观察 I/O 行为,出性能问题的时候多了一个排查入口。第三,也是比较容易忽略的,15 对publicschema 的默认权限做了收紧。这个改动在上生产前基本没人关注,但升级后很可能会让旧业务报权限错误,我后面会单独讲。

当然,升级也不是只看新功能。PostgreSQL 的每个大版本都有自己的维护周期,14 已经走向维护中后期,在这个节点跳到一个更新的版本,后续能获得的补丁支持和社区维护时间更长。对生产环境来说,这比任何单一新特性都重要。

还有一个很现实的考虑:应用侧近期在规划新报表模块,开发同学在 15 上进场会更顺畅,因为 15 在MERGE语法、JSON 处理、排序性能上有不少改进。产品和技术两头都有诉求的时候,升级的判断就比较清晰了。

1.2 三种升级方式怎么选

PostgreSQL 大版本升级,常规路径其实就三条:

升级方式原理停机时间复杂度适用场景
dump/restore用pg_dump/pg_dumpall导出逻辑数据,再导入新库较长,取决于数据量和导出速度低小数据量、跨大版本、需要顺便清洗数据
逻辑复制旧库作为发布端,新库订阅数据,增量追平后切换短,适合在线切换高大库、在线迁移、双写过渡
pg_upgrade直接升级数据目录,物理文件层面迁移短,取决于磁盘和--link模式中同平台大版本升级,最常用

我这次选择的是pg_upgrade,而且用了--link模式。原因有三个:

首先是数据量。这套实例上有几个核心业务库,加起来接近 400GB 左右,既有大表也有不少历史分区表。如果用 dump/restore,导出加导入的时间会非常可观,而且大表的pg_restore索引重建阶段极其考验耐心。用逻辑复制也能做,但要为两端版本差异、约束冲突、序列补偿写不少额外处理,切换那一天的压力会很大。

其次是对象完整性。pg_upgrade是在物理文件层面迁移数据目录,数据库里的扩展、大对象、权限、统计状态这些元数据基本原样保留,不需要重建。这比逻辑导出后重新补齐依赖要省心得多。

最后是版本跨度。PostgreSQL 官方支持 14 到 15 的单次直接升级,中间不需要绕到其他版本过渡。这恰好是pg_upgrade最理想的使用场景。

当然,pg_upgrade也有它的问题:必须停机、新旧数据目录必须在一台机器上(或者共享存储)、对 locale 和编码的一致性要求极严。但从这次实际情况看,这些约束都是可控的,提前检查就能避免大半问题。

2. 升级前的环境盘点与备份设计

2.1 先把自己的环境盘清楚

升级前我做了一个半小时左右的环境梳理,核心是搞清楚四件事:数据库版本和补丁号、所有库的字符集和 locale、安装过的扩展、实例级参数的特殊配置。

版本检查:

sudo -u postgres /usr/pgsql-14/bin/psql -c "SELECT version();"

这条命令会返回 PostgreSQL 14.x 的具体补丁版本。要注意的是,pg_upgrade对跨版本升级的补丁号有要求:14 端最好是较新的补丁版本,不要停留在某个很老的小版本上直接升。如果有 gap,先yum update postgresql14把 14 的小版本补丁打齐,再做跨大版本升级。

然后查字符集和排序规则,这一步后面 initdb 时要用:

SHOW server_encoding; SHOW lc_collate; SHOW lc_ctype;

我这边输出是UTF8 / en_US.UTF-8 / en_US.UTF-8。你在自己的环境里执行时,把这三个值记下来,后面初始化 PG15 数据目录时必须一致。

再看扩展和预加载库:

SELECT extname, extversion FROM pg_extension ORDER BY 1; SHOW shared_preload_libraries;

这一步能看到库里到底装了哪些扩展,以及pg_stat_statements这类预加载模块是不是在shared_preload_libraries里。升级后如果没有在新版本的 contrib 包里找到对应库,PG15 可能直接起不来,或者报 “could not load library” 错误。

最后评估磁盘空间:

df -h /var/lib/pgsql

pg_upgrade执行期间,新旧两个数据目录会同时存在,而且新数据目录里可能还会生成临时 dump 文件。如果磁盘余量不足,升级会在中途直接失败。我给自己定的标准是至少预留旧库占用空间 50% 以上的空余,用--link模式的话可以放宽,但依然不建议卡着临界值去操作。

2.2 备份方案不能只有一个兜底

升级前我做了两级备份,这是我认为整个流程里最不能省的一步。

第一级是全局对象备份。pg_upgrade虽然会处理数据目录,但为了在万一需要回滚时有完整的角色、表空间、数据库参数记录,我用pg_dumpall --globals-only导出了一份:

sudo -u postgres /usr/pgsql-14/bin/pg_dumpall --globals-only -f /backup/pg14-globals-202501.sql

第二级是物理全量备份。我选择pg_basebackup,因为它能在不停机的情况下做热备,而且备份结果是完整的数据目录副本,恢复时可以直接替换整个数据目录:

sudo -u postgres /usr/pgsql-14/bin/pg_basebackup -D /backup/pg14-basebackup -X stream -Fp

备份做完之后,我没有只确认文件存在就收工,而是额外跑了一遍备份内容校验。pg_basebackup产生的备份目录里有一个backup_label和backup_manifest,可以用官方校验工具检查:

/usr/pgsql-14/bin/pg_verifybackup /backup/pg14-basebackup

这一步很多人会忽略,但它能及时发现备份文件不完整、校验和不匹配等问题。备份不是做给监控看的,是要在灾难发生时真的能救命的。我见过不少同事备份脚本跑了半年,真正需要恢复时才发现备份文件是坏的,这不叫备份,叫心理安慰。

2.3 升级前必须确认的几个风险点

除了环境信息,升级前我还把一个容易大意的问题提前处理掉了:业务连接。

升级操作最终会停掉 PG14 实例,所以必须在维护窗口内确保没有应用连接。我当时的做法是提前一天通知业务方,运维窗口开始时先批量停掉应用服务,再用一条 SQL 清掉残余连接:

SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE backend_type = 'client backend' AND pid <> pg_backend_pid();

另外,如果库里有自定义表空间,一定提前看路径。pg_upgrade要求新旧实例的表空间路径保持一致,如果 PG15 的数据目录路径和 PG14 完全一致(我这里是/var/lib/pgsql/15/data和/var/lib/pgsql/14/data),默认情况下没问题。但如果你把新数据目录放在别的挂载点,或者表空间引用了绝对路径,需要额外确认目录存在、属主正确、SELinux 标签也正确。

3. PostgreSQL 15 软件安装与并行部署

3.1 添加官方 YUM 源并安装软件包

Oracle Linux 7.9 属于 EL7 系列,可以直接使用 PostgreSQL 官方提供的 YUM 仓库。安装仓库 RPM:

sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

装完仓库之后,我习惯用yum-config-manager把暂时用不到的 PostgreSQL 版本子仓库关掉,避免后续yum install时误装其他版本:

sudo yum -y install yum-utils sudo yum-config-manager --disable pgdg12 pgdg13 pgdg14 pgdg15 pgdg16

注意,你原来就在用 PG14,所以pgdg14这个子仓库要保留。这里我把pgdg14和pgdg15同时启用,其他的先关掉,环境干净一些。

然后安装 PG15 软件包:

sudo yum -y install postgresql15 postgresql15-server postgresql15-contrib

postgresql15-contrib一定要装,原因后面会提到:很多常用扩展如pg_stat_statements都在这个包里。如果漏装,升级后实例可能因为找不到预加载库而无法启动。

安装完成后确认路径:

rpm -ql postgresql15-server | grep -E 'bin/(postgres|pg_upgrade|initdb)'

正常情况下,PG15 的可执行文件目录是/usr/pgsql-15/bin,数据目录默认是/var/lib/pgsql/15/data。PG14 是/usr/pgsql-14/bin和/var/lib/pgsql/14/data。

3.2 初始化 PG15 数据目录,让 locale 和旧库一致

这一步是整个升级能不能用pg_upgrade顺利跑通的关键。PG15 数据目录初始化的 locale 和编码必须和旧库一致,否则pg_upgrade --check会直接报 locale mismatch。

我建议不要图省事直接systemctl start postgresql-15让它自动 initdb,而是手动执行初始化,把 locale 参数明确写出来:

sudo -u postgres /usr/pgsql-15/bin/initdb -D /var/lib/pgsql/15/data -E UTF8 --locale=en_US.UTF-8

如果你的旧库查出来是C或POSIXlocale,就相应改成--locale=C。这一步不是走个形式,而是决定数据库的排序和比较行为,差一个字符都不行。

初始化完成后,先手动启动一次 PG15 服务,确认它能正常起来,再停掉,为后面的pg_upgrade做准备:

sudo systemctl start postgresql-15 sudo systemctl status postgresql-15 sudo systemctl stop postgresql-15

这里先启动一次还有一个好处:能提前把 PG15 自带的默认配置文件生成出来,避免升级时遇到“新数据目录尚未初始化”的错误。

4. pg_upgrade 全流程实操

4.1 停掉旧集群,进入维护模式

PG15 数据目录就绪之后,就可以进入正式升级窗口了。先停掉 PG14 服务,并取消开机自启:

sudo systemctl stop postgresql-14 sudo systemctl disable postgresql-14

确认旧实例已经完全退出:

pg_ctl status -D /var/lib/pgsql/14/data

这一步的输出如果显示pg_ctl: no server running,说明旧实例已经停止。如果还有残留进程,先排查有没有应用没断干净,再用kill清理后继续。

4.2 先跑一遍 --check,把所有兼容性问题暴露出来

正式执行pg_upgrade前,我会先跑检查模式。这一步不做任何数据迁移,只是把版本、locale、权限、扩展、预加载库这些兼容性条件逐项检查一遍:

sudo mkdir -p /var/lib/pgsql/upgrade_logs sudo chown postgres:postgres /var/lib/pgsql/upgrade_logs cd /var/lib/pgsql/upgrade_logs sudo -u postgres /usr/pgsql-15/bin/pg_upgrade \ -b /usr/pgsql-14/bin \ -B /usr/pgsql-15/bin \ -d /var/lib/pgsql/14/data \ -D /var/lib/pgsql/15/data \ --check

注意:pg_upgrade会在当前工作目录生成一系列脚本和日志文件,所以最好在专门的目录里执行,而不是随便找个 root 家目录。我用的是/var/lib/pgsql/upgrade_logs,创建后属主改成 postgres,保证 postgres 用户能往里写文件。

--check跑完如果看到类似这样的输出,说明检查通过:

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 ...

如果检查阶段爆出错误,先解决再往下走。不要试图跳过检查直接执行升级,后面大概率会花更多时间排查。

4.3 正式执行升级:--link 模式的取舍

检查通过后,执行正式升级命令。我这次选用--link参数,它是pg_upgrade效率最高的模式:

cd /var/lib/pgsql/upgrade_logs sudo -u postgres /usr/pgsql-15/bin/pg_upgrade \ -b /usr/pgsql-14/bin \ -B /usr/pgsql-15/bin \ -d /var/lib/pgsql/14/data \ -D /var/lib/pgsql/15/data \ --link \ -j 8

解释一下几个关键参数:

  • -b:旧版本 PostgreSQL 的可执行文件目录,这里是/usr/pgsql-14/bin
  • -B:新版本 PostgreSQL 的可执行文件目录,/usr/pgsql-15/bin
  • -d:旧数据目录
  • -D:新数据目录
  • --link:使用硬链接而不是物理复制数据文件,速度极快,但代价是升级成功后旧数据目录不能再次启动
  • -j:并行进程数,一般按 CPU 核数的一半或四分之三设置,我自己机器 16 核,用的 8

--link的原理其实不复杂:新数据目录里的文件通过硬链接指向旧数据目录的 inode,文件本身没有被复制,只是多了一个目录项引用。所以在数据量大的场景下,它的耗时基本不受库大小影响,几秒钟就能完成大部分文件链接。这是 dump/restore 完全做不到的。

但代价也很明确。因为新旧两个数据目录指向同一个 inode,升级一旦成功,旧数据目录就不再是一个独立可用的集群。你如果还在旧目录上启动 PG14,会同时改动两个集群共享的数据文件,造成的损坏是不可逆的。所以使用--link前必须有完整的物理备份,并且明确接受“升级成功后不回滚”的决策。

执行过程中,终端会打印出pg_upgrade的每个阶段:

Creating dump of global objects: ok Creating dump of database schemas: ok Linking user relation files: ok ...

我看到Creating dump of global objects和Creating dump of database schemas时还有点紧张,因为这说明pg_upgrade内部还是会走pg_dump/pg_restore来处理一部分元数据和对象定义。但整个等待时间不算长,真正耗时的部分被--link消掉了。

执行完pg_upgrade后,当前目录会出现几个脚本文件。有两个必须关注:

  • analyze_new_cluster.sh:对新集群执行ANALYZE,收集统计信息。升级后不跑这个,优化器可能基于过期统计信息给出很差的执行计划
  • delete_old_cluster.sh:删除旧集群数据目录。建议先别急着执行,等确认新集群没问题再处理

4.4 升级后的配置对齐和启动

先执行统计信息收集脚本:

cd /var/lib/pgsql/upgrade_logs sudo -u postgres sh analyze_new_cluster.sh

然后启动新集群:

sudo systemctl start postgresql-15 sudo systemctl enable postgresql-15

启动之前,我强烈建议先对新旧两个postgresql.conf做一次差异对照。pg_upgrade不会把旧实例的配置参数自动迁移到新库,它只会迁移数据。所以旧库里的max_connections、shared_buffers、work_mem、listen_addresses、archive_mode这些生产参数,需要手动在新配置里重新对齐:

diff /var/lib/pgsql/14/data/postgresql.conf /var/lib/pgsql/15/data/postgresql.conf

对比时我遇到一个小细节:PG15 里部分旧参数已经改名或者被移除,直接照搬旧配置会让实例启动失败。比如旧版本里配置的某个扩展相关参数,在新版本里已经被内置了。这种时候看journalctl -u postgresql-15的报错信息,逐个调整即可。

pg_hba.conf同样需要检查。PG15 自带的默认认证配置很保守,如果你的旧库有业务账号、远程访问需求,新库默认不会自动带上这些规则。我的做法是对照旧pg_hba.conf的关键行,手动把host规则复制到新配置,再做认证验证。

最后做一轮连接验证:

/usr/pgsql-15/bin/psql -U app_user -d appdb -p 5432 -c "SELECT version();"

这里我专门强调了/usr/pgsql-15/bin/psql,因为系统 PATH 里的psql可能还指向旧版本。如果应用的连接池配置里写的是裸psql命令,记得把 PATH 或调用脚本里的路径同步改掉,否则会出现“命令还在用旧版本”的隐患。

5. 常见问题与排查技巧实录

5.1 locale 不匹配导致的升级中断

这是我在检查环境时最担心的问题,也是很多人升级 PG 时最容易踩的坑。报错信息通常是:

error: LC_COLLATE effective for database "postgres" does not exist or is not the same as old cluster

原因很简单:PG15 数据目录初始化时选择的 locale 和 PG14 不一致。比如旧库是en_US.UTF-8,新库默认初始化成了C或POSIX。

解决方式就是重新初始化 PG15 数据目录,用和旧库完全一致的 locale 参数。重新initdb后再次执行--check,这个问题基本就消失了。所以我在前面反复强调,initdb 一定要手动指定 locale,不要依赖自动初始化。

5.2 扩展库缺失导致的启动失败

新库启动时报错:

could not load library "$libdir/pg_stat_statements"

原因是 PG15 的数据目录里配置了shared_preload_libraries = 'pg_stat_statements',但 PG15 服务器上没装包含这个扩展的包。pg_stat_statements在postgresql15-contrib里。

解决方案:

sudo yum -y install postgresql15-contrib

装完重启 PG15 即可。如果你的环境里还用了其他第三方扩展,检查一下/usr/pgsql-15/lib目录下有没有对应的.so文件。如果没有,需要从扩展源码重新编译适配 PG15 的版本。

5.3 public schema 权限变化导致的业务报错

这个容易被忽略。PostgreSQL 15 开始,普通用户不再默认拥有publicschema 的CREATE权限。而在 14 及更早版本里,所有用户都能在publicschema 下建表。升级后如果业务账号需要在 public schema 里创建临时表或对象,会收到:

permission denied for schema public

这不是pg_upgrade的锅,是 PG15 默认行为收紧。处理方式取决于你的安全策略:

-- 方式一:恢复旧行为,允许所有人创建 GRANT CREATE ON SCHEMA public TO PUBLIC; -- 方式二:只给指定业务角色 GRANT CREATE ON SCHEMA public TO app_user;

我实际建议按方式二处理,只给真正需要建表的角色授权,而不是直接把public再次全面放开。PG15 收紧这个权限本身是为了安全,升级后顺手把权限模型理清,总比留一个宽泛的授权死角要好。

5.4 SELinux 干扰自定义数据目录

Oracle Linux 7.9 默认 SELinux 可能处于 enforcing 模式。如果你的 PG15 数据目录没有放在默认的/var/lib/pgsql/15/data,而是放到了/data或其他自定义挂载点,启动时会因为 SELinux 上下文不正确而失败。

排查方法:

sudo journalctl -u postgresql-15 -n 100

日志里如果看到类似SELinux is preventing postgres from ...,先临时放行验证一下:

sudo setenforce 0 sudo systemctl start postgresql-15

能启动就说明是 SELinux 上下文的问题。随后恢复 enforcing 模式,并给数据目录设置正确的策略标签:

sudo semanage fcontext -a -t postgresql_db_t "/data/pg15(/.*)?" sudo restorecon -Rv /data/pg15

然后再启动 PG15。只要数据目录都是用标准 PATH,一般不会遇到这个问题,但自定义路径必须提前规划。

5.5 常见问题速查表

现象可能原因处理办法
--check报 locale mismatchPG15 initdb 的 locale 与旧库不一致重新 initdb,指定与旧库相同的 locale
新库启动报 could not load library缺少扩展的.so文件安装 postgresql15-contrib 或编译对应扩展
业务连不上,提示 pg_hba.conf 拒绝PG15 pg_hba.conf 未配置业务认证规则对照旧 pg_hba.conf 补齐认证配置
业务建表报 permission denied for schema publicPG15 public schema 默认权限收紧按需GRANT CREATE给指定角色
新库起不来,journal 里有 SELinux 报错自定义数据目录上下文不对semanage fcontext+restorecon修正
升级后统计信息不准,慢查询变多未执行 analyze_new_cluster.sh运行脚本或手工vacuumdb -a -v

5.6 关于回滚的一点个人看法

我知道很多人关心升级失败能不能回滚。这取决于你用的是--link还是默认 copy 模式。copy 模式下旧库没有被动过,直接切回去就行;--link模式则因为新旧共用 inode,升级成功后不能启动旧库。

我的习惯是:有完整pg_basebackup的前提下,优先--link,速度快到可以大幅缩短维护窗口。但回滚方案要提前准备好,备份文件要验证过,业务侧要知道升级决策点在哪。真遇到升级失败,先用备份恢复旧库,不要指望旧数据目录还能继续用。这是所有用--link模式升级的人必须明确接受的一点。

升级之外还想多说一句

这次升级执行下来,我最深的体会是:PostgreSQL 大版本升级的复杂度从来不在命令本身,而在于你对自己的环境了解多少。locale 对不对、扩展装没装、权限有什么变化、配置迁移怎么做,哪一项没提前考虑清楚,升级过程就会在某个你以为最稳的节点上给你意外。

如果你手里的实例数据量不大,升级窗口也宽裕,完全可以选更保守的 dump/restore;如果数据量像我遇到的这种几百 GB 级别,pg_upgrade --link是效率最高的选择。关键是在动手前把备份、检查、权限梳理这些前置工作做实,别把希望寄托在“应该没问题”上。升级完成后,也别急着把旧目录删掉,多留观察期,看看新集群在业务流量下的稳定性,确认慢查询、连接池、备份链路都正常了,再做清理。

最后一个小建议:把这套升级步骤沉淀成一份 checklists,下次再遇到跨版本升级,逐项打勾执行,比自己翻文档高效得多,也不容易漏步骤。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询