PostgreSQL大版本升级时,很多运维人员会选用pg_upgrade工具,它能在不导出全部数据的情况下完成版本迁移,速度快、停机窗口短。但升级并不总是顺利的,比如磁盘空间不足、新旧版本字符集不匹配、扩展插件在新版本不可用,甚至升级完成后业务方发现新版行为变化导致功能异常,这时候就需要把数据库回滚到旧版本。pg_upgrade本身没有提供undo命令,回滚的核心思路是:只要旧的数据目录还在,直接删掉新集群、重新启动旧集群即可。下面详细介绍具体操作和原理。

理解pg_upgrade的升级原理才能正确回滚
pg_upgrade的工作方式是在旧数据目录和新数据目录都存在的前提下,对比两个版本的系统目录结构,把旧库的元数据转换写入新目录。如果升级时使用了--link参数,新旧数据目录中的数据文件会通过硬链接共享同一份磁盘空间,从而避免复制数据;如果没加--link,则是完整复制一份新的数据文件。
关键点在于:升级过程中,旧数据目录默认不会被删除或修改(只有system表被重命名为_old结尾),新数据目录是升级的目标。也就是说,只要旧数据目录完整保留,它就是一份可以直接启动的旧版本数据库。回滚之所以简单,正是因为升级操作本质上是单向写入了新目录,而旧目录只是被移动了system catalog的文件名。
需要特别注意的是,如果升级成功后你已经在新集群上运行了业务并写入了新数据,那么这部分数据在旧目录中是不存在的,回滚就意味着放弃这些新增数据。所以回滚决策要尽早做,升级完成后的验证阶段发现问题时回滚成本最低。
升级失败的回滚具体操作步骤
假设旧版本为PostgreSQL 13,新版本为PostgreSQL 16,旧数据目录在/pgdata/13/data,新数据目录在/pgdata/16/data,完整的回滚流程如下。
第一步,确认新旧集群都已停止。使用link模式升级时尤其要保证两个实例都没有进程存活,否则硬链接关系可能导致文件被意外修改。检查进程命令如下:
# 检查是否还有 postgres 进程 ps -ef | grep postgres # 分别停止两个版本的实例(如果还能启动的话) /usr/pgsql-13/bin/pg_ctl -D /pgdata/13/data stop -m fast /usr/pgsql-16/bin/pg_ctl -D /pgdata/16/data stop -m fast
第二步,处理旧数据目录中的system表重命名残留。pg_upgrade运行时会将旧目录中pg_control改名为pg_control.old,并将旧的system catalog文件加上_old后缀(如pg_class_old等,存放在base目录下的各数据库子目录中)。如果升级中断在早中期,这些改名可能只完成了一部分,此时旧集群无法直接启动,需要执行恢复操作把文件名改回去。官方提供的处理方式是删除所有_old结尾的文件,再把pg_control.old改回pg_control:
# 进入旧数据目录,删除重命名后的旧 system catalog 文件
cd /pgdata/13/data/base
# 删除所有以 _old 结尾的文件
find . -name "*_old" -type f -exec rm -f {} \;
# 恢复 pg_control 文件名
mv /pgdata/13/data/global/pg_control.old /pgdata/13/data/global/pg_control这里务必小心:删除的是带_old后缀的文件,这些是升级开始时被改名隔离的旧system catalog副本,当前有效的catalog文件是不带后缀的。如果不确定目录状态,先做磁盘快照再操作更稳妥。
第三步,删除或移走新数据目录。新目录在回滚后没有价值,直接删除可以释放空间。但如果还想排查升级失败原因,建议先整体移动到备份路径:
# 移走新数据目录以便排查问题 mv /pgdata/16/data /pgdata/16/data_failed_$(date +%Y%m%d) # 或者确认不再需要时直接删除 # rm -rf /pgdata/16/data
第四步,用旧版本的二进制重新启动旧集群,并验证状态:
# 注意必须使用旧版本的 pg_ctl 启动 /usr/pgsql-13/bin/pg_ctl -D /pgdata/13/data start # 验证版本和数据 /usr/pgsql-13/bin/psql -d postgres -c "SELECT version();" /usr/pgsql-13/bin/psql -d postgres -c "SELECT count(*) FROM pg_database;"
最后一步,恢复业务连接。如果之前修改了监听端口、pg_hba.conf或者应用连接串指向了新集群,记得全部改回旧集群的配置。对于使用systemd管理的环境,还要确认服务单元文件指向的是旧版本的bin目录和数据目录。
link模式升级后的回滚注意事项
使用--link参数升级成功后,新旧目录的数据文件通过硬链接指向同一批磁盘块。这种情况下回滚有一个重要的坑:升级成功后旧目录实际上已经被认为是废弃状态,pg_upgrade在升级结束时会提示删除旧目录。此时如果回滚,只要旧目录还没删除,虽然文件被硬链接共享,只要没有在新集群上写入大量数据导致硬链接分裂,旧目录依然可能可用。
但要强调,link模式一旦在新集群上发生过大量写入、更新、删除操作,对应的数据块会因写时复制而与旧目录分道扬镳,旧目录的数据将不再反映最新状态,甚至处于不一致的崩溃恢复点。因此link模式升级后,必须在新集群上执行pg_backup_start之前就决定是否回滚,最稳妥的做法是升级完成、业务验证通过之后再删除旧目录,回滚窗口就维持在这个验证期内。
另外,link模式下新旧目录必须在同一文件系统上。如果回滚时发现旧目录中缺少文件(比如被误删),可以从新目录对应的文件通过硬链接补回,因为文件内容和升级时是完全一样的。这也说明保留新目录一段时间对回滚排查是有帮助的。
如何让回滚更安全:事前准备清单
与其事后补救,不如在升级前就把回滚路径准备好。升级前务必做好以下几件事:
- 对旧数据目录做完整备份,条件允许时用
pg_basebackup或文件系统快照,快照回滚只需要几分钟。 - 新旧数据目录分别放在独立路径,不要复用同一个目录,避免升级脚本误删旧目录。
- 记录旧集群的所有配置:postgresql.conf的自定义参数、pg_hba.conf、表空间路径、扩展插件列表,回滚后需要逐一核对。
- 在升级命令中使用
--check参数先做干跑检查,把版本不兼容、locale不一致等会导致失败的问题提前暴露。 - 规划好回滚决策点和决策人,升级完成后的业务验证项提前列成清单,验证不通过立即触发回滚流程。
还需要提醒一点,升级失败的原因要留档分析。pg_upgrade的输出日志以及新数据目录中的pg_upgrade_output.d文件夹记录了详细过程,把它和失败的新目录一起归档,下次重试升级时可以避开同样的问题。回滚只是应急手段,彻底解决问题后重新规划升级,才能让数据库版本跟上节奏。
pg_upgradePostgreSQL升级数据回滚修改时间:2026-09-11 12:56:41