☰
PostgreSQL 14→15升级实战:pg_upgrade权限认证与扩展兼容避坑指南
2026/10/14 3:15:10 网站建设 项目流程

前两周刚把一套业务系统的 PostgreSQL 14 集群升级到 15,平台是 Oracle Linux 7.9。说实话,接这个任务之前我以为是“旧版本停掉、新版本启动、数据拷过去”就能收工的事,真正动手才发现,14 到 15 之间有一批行为变化会直接影响应用,而且 Oracle Linux 7.9 这套老环境还有一些自己的脾气。整个过程踩了几个不大不小的坑,最后好歹是平稳切换到 PG15 上了。这篇就把实际操作记录一下,重点不是背命令,而是讲清楚每一步为什么要这么做,以及哪些地方最容易翻车,给准备做 14→15 升级的朋友一个可以直接参考的路径。

先说结论:14→15 的升级,核心工具就是 pg_upgrade,只要准备工作做得细,整个切换过程可以控制在很短的停机窗口内;反过来,如果忽略了扩展模块、认证方式、public 模式权限这类细节,升级后第一波应用报错会让你手忙脚乱。下面按我的实际操作顺序展开讲。

1. Oracle Linux 7.9 上把 PG14 换成 PG15:先想清楚这三件事

1.1 目录布局与运行身份检查

我的习惯是升级前先把现状摸清楚,尤其是目录和运行身份,因为 pg_upgrade 对路径、权限、用户的要求比普通运维操作严格得多。

先看一下当前版本和安装路径:

/usr/pgsql-14/bin/postgres --version psql --version

输出是 PostgreSQL 14.x 之后,确认旧版本的可执行文件在/usr/pgsql-14/bin下。再看数据目录,默认在/var/lib/pgsql/14/data。Oracle Linux 7.9 里 PostgreSQL 服务一般通过 systemd 管理,systemctl status postgresql-14可以看到当前实例状态。

这里有一个容易被忽略的点:Oracle Linux 7.9 默认创建了postgres系统用户,所有 PostgreSQL 相关进程和数据文件都属于这个用户。pg_upgrade 必须以同一用户身份运行,也就是说我们要用su - postgres切过去执行,不能用 root 直接跑。如果数据目录的属主不对,pg_upgrade 会在检查阶段直接报权限错误。

还要检查旧数据目录的磁盘占用,这一步直接决定后面选哪种升级策略:

du -sh /var/lib/pgsql/14/data df -h

1.2 PostgreSQL 15 相比 14 的关键变化清单

很多人升级只关心数据能不能迁过去,但我更关注“行为变化”,因为数据能迁不代表应用能跑。PG15 和 PG14 相比,下面几个变化对我的实际迁移产生了直接影响。

第一个是 public 模式的默认权限。PG15 之前,public 模式对所有角色都是开放的,任何用户都能在里面 CREATE 对象;PG15 开始默认收回了 PUBLIC 的 CREATE 权限。如果旧库里应用一直靠“public 模式默认可写”来建表,升级后立刻会报permission denied for schema public。这不是数据问题,是权限模型变了,必须显式授权。

第二个是密码认证方面。PG14 时代很多旧集群还在使用 md5 方式的密码哈希,应用连接串里也可能写着password_encryption=md5。PG15 对 SCRAM-SHA-256 的推进更彻底,升级后如果 pg_hba.conf 还保留一堆md5条目,或者密码哈希类型没迁移,轻则日志刷屏,重则应用连不上。

第三个是 WAL 和监控方面的小改动,比如新增了pg_stat_io视图,以及一些 GUC 参数的移除。vacuum_cleanup_index_scale_factor在 PG14 里已经废弃,到 PG15 被彻底移除,旧postgresql.conf里如果还留着这个配置,新实例启动时会报 unrecognized configuration parameter 的警告。这种问题不难解决,但会让你在启动验证时多费一轮排查。

下面这张表是我升级前给自己列的检查清单,也分享出来:

检查项PG14 现状PG15 要求风险等级
public 模式 CREATE 权限默认开放默认收回高
密码哈希类型可能是 md5 或 scram建议统一 scram-sha-256高
pg_hba.conf 认证方式md5 / scram / trust 混合需逐条确认兼容性中
shared_preload_libraries指向 /usr/pgsql-14/lib需改成 /usr/pgsql-15/lib高
扩展模块14 版本编译需要 15 版本可用高
废弃 GUC 参数可能还配置着PG15 已移除低

1.3 扩展、预加载库和认证方式的盘点

这一步直接决定 pg_upgrade 能跑多远。pg_upgrade 在正式执行前有一个检查阶段,里面最麻烦的就是扩展模块不匹配。

我先列出了当前库里安装的所有扩展:

SELECT extname, extversion FROM pg_extension ORDER BY extname;

同时检查了静态加载库:

grep shared_preload_libraries /var/lib/pgsql/14/data/postgresql.conf

当时发现shared_preload_libraries配了pg_stat_statements,路径在/usr/pgsql-14/lib/pg_stat_statements.so。这个文件对版本敏感,PG15 的库目录是/usr/pgsql-15/lib,路径必须改。如果用的是第三方闭源扩展,还得确认有没有支持 PG15 的新版本,否则升级后实例可能直接起不来。

认证方式也要盘点,比如查看pg_hba.conf里哪些条目用了 md5,哪些用户依赖旧的密码校验。我习惯用下面这条 SQL 查所有角色的密码哈希类型:

SELECT rolname, split_part(rolpassword, ':', 1) AS hash_algo FROM pg_authid WHERE rolpassword IS NOT NULL;

如果是md5开头,就意味着这个角色还在用 md5 哈希,升级前最好统一重置密码到 SCRAM,避免升级后某些应用连接方式不兼容。这里多说一句:不建议在升级窗口内顺手改密码,容易引入额外变量,建议提前一到两周完成密码重置,让应用充分验证。

2. link 模式还是 copy 模式:升级效率与回滚成本的取舍

2.1 两种模式的工作方式和速度差异

pg_upgrade 给数据文件迁移提供了两种主要模式:默认的 copy 模式,以及加--link参数的硬链接模式。

copy 模式很好理解,就是把旧数据目录里的数据文件完整复制到新数据目录,升级结束后新旧两套文件互不影响。这个模式最稳,但最慢,数据量越大耗时越长,而且需要差不多一倍的额外磁盘空间。如果一个实例有 500GB 数据,copy 模式可能要跑几十分钟甚至更久。

link 模式则完全不同。它不在物理上复制文件,而是在新数据目录里为旧数据文件创建硬链接。数据块还是那一份,新旧目录只是指向同一份数据的不同入口。因此 link 模式速度快到惊人,几个 GB 的库可能几十秒就完成迁移。代价是什么呢?一旦用硬链接,新旧目录共享底层数据文件,升级成功后旧集群的“数据”其实已经跟着新集群一起变了,你不能再启动旧集群来当回滚方案。

我举个例子。你有一份文档,copy 模式是复印一份副本,原本和副本互不干扰;link 模式是做一个快捷方式,快捷方式和原文档指向同一个内容,原文档改动快捷方式看到的也是新的。升级场景里,显然 copy 模式更安全。

2.2 为什么生产环境建议先做一次 copy 演练

我个人的经验是:正式切换用 link 模式,但在切换之前,先在测试环境做一次完整 copy 模式演练。

这看起来有点绕,但理由很实际。copy 模式等于把链路完整走一遍,包括扩展检查、配置迁移、统计信息重建、启动验证,过程中所有报错都会暴露出来。等你确认流程没问题、命令没问题、应用行为没问题,再在正式环境用 link 模式去缩短停机时间。如果你直接上 link,一旦中途某个环节出问题,因为数据文件已经是硬链接状态,处理起来会非常被动。

演练还可以帮你估出一个关键数字:停机窗口到底需要多久。我在演练时记录了从停库到新集群可用的总时长,包括:

  • 停旧库
  • 执行 pg_upgrade
  • 运行 analyze_new_cluster.sh
  • 启动新库

这个总时长就是应用方需要感知的停机时间,提前告诉他们,让大家有预期。

2.3 磁盘空间、备份和回滚窗口的规划

不管用哪种模式,升级前都要做备份。我在这套 Oracle Linux 7.9 环境上用的是 pg_basebackup,做了全量备份到另一台机器,而不是本机,避免备份文件占满本地磁盘影响升级。

如果你用 copy 模式,需要的额外空间大约等于数据目录大小;用 link 模式,理论上不用太多额外空间,但前提是new datadir本身要有一定的初始化空间。上线前我给/var/lib/pgsql所在文件系统留出了足够余量,并确认 XFS 文件系统支持硬链接。Oracle Linux 7.9 默认根文件系统通常是 XFS,硬链接没有问题,但如果你的数据目录在 VFAT 这类不支持硬链接的挂载点上,link 模式会直接失败。

回滚策略也要提前定好。我的建议是:用备份作为回滚主方案,而不是依赖旧数据目录。因为无论 copy 还是 link,一旦升级流程启动,旧集群的稳定性就不该再被信任。万一升级到一半失败,最稳妥的做法是从备份恢复旧集群,把服务拉起,而不是强行去折腾已经动过的数据文件。停机窗口预估时,要把“从备份恢复”的时间也算进去。

3. 实际执行 pg_upgrade 的完整过程与关键细节

3.1 安装 PostgreSQL 15 二进制并初始化新目录

Oracle Linux 7.9 上安装 PostgreSQL 15,我用的还是 PostgreSQL 官方发布的 YUM 仓库。如果机器之前已经用过这个仓库,直接安装就可以:

sudo yum install -y postgresql15-server postgresql15-contrib

装完以后,可执行文件在/usr/pgsql-15/bin,数据目录默认在/var/lib/pgsql/15/data。为了让新旧两套版本并存,我特意没有去动 PG14 的任何文件。

接着初始化新集群目录:

sudo /usr/pgsql-15/bin/postgresql-15-setup initdb

或者手动执行:

sudo -u postgres /usr/pgsql-15/bin/initdb -D /var/lib/pgsql/15/data

这里有一个细节:如果旧集群启用了数据校验和(checksums),新集群初始化时必须加上-k或--data-checksums,否则 pg_upgrade 会报校验和设置不匹配。检查旧集群是否开启校验和,可以直接看旧 postgresql.conf 里的data_checksums参数,或者查询:

SHOW data_checksums;

初始化完成后,先不要启动 PG15 服务,等配置迁移完成再说。

3.2 迁移配置文件而不是照抄

pg_upgrade 不会自动迁移postgresql.conf、pg_hba.conf、pg_ident.conf这三个文件。很多人栽在这个地方:数据是迁过去了,但新库连接的认证规则还是默认的,或者参数根本没跟上,结果应用连不上或者性能不对。

我的做法是有选择地复制,而不是直接照抄:

# 以 postgres 用户操作 cp /var/lib/pgsql/14/data/pg_hba.conf /var/lib/pgsql/15/data/pg_hba.conf cp /var/lib/pgsql/14/data/postgresql.conf /var/lib/pgsql/15/data/postgresql.conf cp /var/lib/pgsql/14/data/pg_ident.conf /var/lib/pgsql/15/data/pg_ident.conf

复制后必须逐项检查postgresql.conf,重点包括:

  • shared_preload_libraries里的.so路径要从/usr/pgsql-14/lib改成/usr/pgsql-15/lib
  • 端口、监听地址是否和旧实例保持一致
  • 去掉 PG15 已经移除的废弃参数,比如vacuum_cleanup_index_scale_factor
  • 确认data_directory不需要写,或者写新目录路径

pg_hba.conf复制过来后,我顺手把所有md5条目改成了scram-sha-256。这一步必须配合密码哈希类型一起做,否则连接会失败。如果你确认应用还在用旧密码且不想改,至少要让认证方式和密码哈希类型匹配,而不是盲目改。

3.3 停旧库、跑 pg_upgrade --check、再正式执行

配置迁移完,先做一次完整的检查,确保兼容性没问题:

su - postgres /usr/pgsql-15/bin/pg_upgrade \ --old-datadir=/var/lib/pgsql/14/data \ --new-datadir=/var/lib/pgsql/15/data \ --old-bindir=/usr/pgsql-14/bin \ --new-bindir=/usr/pgsql-15/bin \ --old-socketdir=/tmp \ --new-socketdir=/tmp \ --check

--check只做检查,不执行任何迁移动作。如果这一步有报错,比如扩展缺失、目录权限不对、版本不匹配,都会在这里显示出来。我遇到过一次典型的报错,说某个扩展在旧集群存在但新集群找不到对应库文件,解决方法是确认新版本对应的扩展包已安装,再重新跑检查。

检查通过之后,停掉旧实例:

sudo systemctl stop postgresql-14

确认进程真的停了:

sudo -u postgres /usr/pgsql-14/bin/pg_ctl -D /var/lib/pgsql/14/data status

然后正式执行升级。如果希望速度快、停机时间短,加--link:

/usr/pgsql-15/bin/pg_upgrade \ --old-datadir=/var/lib/pgsql/14/data \ --new-datadir=/var/lib/pgsql/15/data \ --old-bindir=/usr/pgsql-14/bin \ --new-bindir=/usr/pgsql-15/bin \ --old-socketdir=/tmp \ --new-socketdir=/tmp \ --link

输出里会看到各个数据库逐个迁移的日志,最后出现Upgrade complete之类的成功标记。pg_upgrade 会在新数据目录下生成analyze_new_cluster.sh和delete_old_cluster.sh两个脚本,前者用于更新统计信息,后者用于确认稳定后清理旧集群。注意:执行 pg_upgrade 之后、启动新实例之前,不要把旧集群启动起来,尤其在 link 模式下,那会导致数据文件状态错乱。

3.4 启动新库与升级后的基础验证

完成升级后,先执行统计信息刷新脚本:

cd /var/lib/pgsql/15/data ./analyze_new_cluster.sh

这个脚本会 ANALYZE 所有数据库,给优化器提供最新统计信息,不跑的话很容易出现升级后出现怪异执行计划。脚本跑完再启动新实例:

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

启动后做基础验证,我习惯依次检查这些点:

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

再确认数据库数量和大小:

SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database ORDER BY datname;

然后检查扩展是否正常:

SELECT extname, extversion FROM pg_extension ORDER BY extname;

如果pg_stat_statements在shared_preload_libraries里,还需要确认视图能查:

SELECT * FROM pg_stat_statements LIMIT 1;

到这一步,数据层面的升级已经完成,剩下的就是应用层验证和切换了。

4. 升级后最容易翻车的三个点:权限、认证和扩展

4.1 public 模式权限收紧引发 CREATE 失败

这是 PG15 改动里影响面最大的一项。我们升级完启动新库后,让应用先跑了一轮冒烟测试,结果第一个报错就是应用在 public 模式下执行CREATE TABLE时被拒绝,错误信息类似:

ERROR: permission denied for schema public

原因正如前面说的,PG15 默认不再向 PUBLIC 角色开放 public 模式的 CREATE 权限。很多老应用从 PG9、PG10 一路升级上来,一直依赖这个默认开放行为。

处理方式有两种。如果你希望恢复旧行为,执行:

GRANT CREATE ON SCHEMA public TO PUBLIC;

但我不建议在一套正式系统里这么干,等于把一个安全改进又改回去了。更合理的做法是把 CREATE 权限只授予真正需要的业务角色:

GRANT CREATE ON SCHEMA public TO app_user; GRANT USAGE ON SCHEMA public TO app_user;

同时检查已有对象的权限,确认app_user对已有表有足够权限。这个调整最好在测试环境先验证一遍,再应用到生产,因为你可能在测试环境才发现应用里不止一张表,还有函数、视图、序列都需要重新授权。

4.2 密码哈希类型与 pg_hba.conf 导致的连接失败

第二个典型故障是应用连不上库。我们的测试环境当时也碰到了,报错内容集中在:

FATAL: password authentication failed for user "xxx"

这是因为旧集群里该用户还是 md5 哈希,而新集群的 pg_hba.conf 你已经改成了scram-sha-256,两者不匹配。解决办法也简单,把用户密码重新设置为 SCRAM 哈希:

SET password_encryption = 'scram-sha-256'; ALTER USER app_user WITH PASSWORD 'new_password';

然后让应用使用新密码连接。如果你不想改密码,也可以把 pg_hba.conf 对应条目改回md5,但这样做相当于保留旧认证能力,在安全性和长期维护上都吃亏,所以我还是建议直接切到 SCRAM。

这里还有一个值得注意的地方:升级后连接串、连接池里的旧密码可能已经不适合新认证方式。即使数据库层面账号没问题,应用配置里的密码也要同步更新。我遇到过一个情况,数据库密码明明重置了,应用还报认证失败,排查半天发现是连接池缓存了旧连接。所以切换前最好把连接池也一起重启,避免旧连接残留。

4.3 扩展版本不匹配与预加载库路径错乱

第三种常见问题来自扩展。pg_upgrade 在检查阶段就会抓出扩展缺失,但有些场景要等到新库启动才暴露。

比如shared_preload_libraries里写的路径还是/usr/pgsql-14/lib/xxx.so,而/usr/pgsql-15/lib下对应文件不存在,实例启动会直接失败,错误大概是这样:

could not access file "$libdir/plpgsql": No such file or directory

或者说找不到某个动态库。排查思路很直接:先看postgresql.conf里的shared_preload_libraries,再和/usr/pgsql-15/lib目录实际文件对比。如果某个扩展在新版本里没装,补装对应包后重新启动即可。

如果是postgis、timescaledb这类重型扩展,版本兼容性更要提前确认。这些扩展往往和特定 PostgreSQL 大版本强绑定,升级前需要把兼容 PG15 的新版本装好,并且升级后在数据库里执行扩展的升级脚本。我这次虽然没有用重量级第三方扩展,但同事在另一套环境升级时,就吃过扩展版本不兼容的亏,所以这块一定要列入升级前的检查清单,别等到 pg_upgrade 跑挂了再回头补。

5. 切换上线、灰度验证与旧集群清理的纪律

5.1 应用连接串切换的顺序和回滚原则

新库已经能正常启动,扩展、权限、认证也都验证过,接下来就是让应用真正切过去。我的做法分三步走:

第一步,先切一台只读业务或者非核心应用过去。把连接串里的端口或主机指向 PG15 实例,观察一段时间,确认查询正常、日志无 ERROR。如果这一步就有问题,影响范围很小,回滚很干净。

第二步,再切核心应用。这时候同样要看应用日志、监控指标,重点观察慢查询数量、连接数、报错数量。

第三步,确认稳定后,通知所有下游系统切换,而不是大家一窝蜂同时改连接。

回滚原则明确:一旦应用切到 PG15,发现严重问题需要回滚,直接走备份恢复 PG14,而不是把 PG14 旧数据目录拉起来。因为 PG14 旧目录很可能已经被硬链接或者后续操作影响,不能保证数据一致。所以备份的可用性是回滚的底线。

5.2 灰度验证清单与性能快照对比

升级 PG 大版本,最怕的不是丢数据,而是“数据都在,但性能变了”。我在升级前采集了一套性能基线,升级后跑同样的业务查询做对比。建议你也留一份升级前的快照,用下面这几类数据做参考:

  • 关键业务表的行数、大小
  • 慢查询日志中出现频率最高的 SQL
  • pg_stat_user_tables里的 seq_scan、idx_scan、last_analyze
  • 连接数、活跃会话数、WAL 生成速率

升级后最容易出现的性能问题,其实是统计信息不准导致的执行计划变化。analyze_new_cluster.sh已经做了全量 ANALYZE,但如果你用的是自定义维护窗口,建议在新库上线后的第一个非高峰时段,再对核心业务表做一次ANALYZE,进一步稳定执行计划。

灰度验证时我还做了一组对照查询,比如:

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE order_status = 'PENDING' ORDER BY created_at DESC LIMIT 100;

把升级前后的执行计划、耗时、扫描方式对比一下,能很快发现计划漂移的问题。如果发现某条 SQL 走了更差的计划,可以通过更新统计信息、调整参数或者加索引来解决。

5.3 确认稳定后如何清理旧数据目录和服务

升级完别急着删旧集群。我的习惯是新版本至少稳定运行一周,期间跑过完整的业务周期(包括日终任务、周报任务这类低频作业)之后,再考虑清理。

清理时操作顺序有讲究。首先,确认 PG15 已经设为开机自启:

sudo systemctl enable postgresql-15

然后停掉并禁用 PG14 服务:

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

接着执行 pg_upgrade 生成的清理脚本:

cd /var/lib/pgsql/15/data ./delete_old_cluster.sh

这个脚本会删除旧集群的数据文件。执行前再确认一次备份存在,避免手滑。如果旧数据目录还有业务需要保留的归档、日志,先拷出来再清理。同时检查系统里还有没有 PG14 相关的 YUM 包,确认不需要后可以移除:

sudo yum remove -y postgresql14-server postgresql14-contrib

最后检查一下alternatives配置,确保psql默认指向 PG15 的版本,避免应用还无意中调用了旧版本客户端(不过这不会影响 server 端,但会让运维排查时多一层困惑)。

到这一步,PostgreSQL 14 到 15 的升级才算是完整收尾了。我个人在这次升级里最大的体会是:pg_upgrade 本身很成熟,真正决定成败的反而是它不负责的那部分——配置迁移、权限适配、扩展兼容、连接串切换。把这些前置问题在演练阶段全部解决掉,生产切换就是按脚本走流程的事。如果你也正准备做同样的升级,强烈建议至少提前一周把测试环境的完整流程跑通,尤其是应用冒烟测试,那是检验升级质量最直接的手段。

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

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

立即咨询