PostgreSQL大版本升级通常需要处理数据目录中的大量文件,常规的复制模式会占用额外磁盘空间,并且数据量越大耗时越长。pg_upgrade工具提供了--link选项,利用文件系统硬链接直接关联旧集群与新集群的数据文件,从而跳过复制过程。这种方式在数据量达到数百GB时优势尤其明显,几分钟内即可完成核心升级操作。本文详细说明该模式的工作原理、使用条件、完整升级流程以及风险控制方法。

一、硬链接模式到底做了什么
普通文件系统中的文件由inode和目录项共同描述。一个文件的数据是否被复制,取决于是否创建了新的inode以及新的数据块。硬链接则是在同一种文件系统内创建另一个目录项,让它指向同一个inode,因此不会产生第二份数据副本。pg_upgrade --link正是利用这一特性,把旧版本数据目录里的表数据文件、索引文件等通过硬链接关联到新版本数据目录中。
这样处理后,旧集群和新集群在物理上共享相同的数据块,升级过程中不需要逐个复制文件,既显著降低时间成本,也几乎不增加磁盘占用。可以通过ls -li查看两个文件的inode号来验证硬链接关系。以下命令会在同一个文件系统内创建一个硬链接,并展示两个目录项拥有相同的inode号。
# 创建测试文件 echo hello > /tmp/test_file # 创建硬链接 ln /tmp/test_file /tmp/test_file_link # 查看inode号 ls -li /tmp/test_file /tmp/test_file_link
硬链接模式的关键限制也由此产生:硬链接不能跨文件系统创建。如果旧数据目录和新数据目录位于不同的挂载点,或者某些表空间指向了不同的文件系统,pg_upgrade --link就会失败。因此使用前必须确认相关目录都在同一文件系统内。
还需要注意,硬链接并非快照。当新集群启动并修改共享的数据页时,旧集群看到的内容也会发生变化。官方文档明确说明,使用链接模式升级后,旧集群不再能够被安全地启动。升级前必须做好完整备份,不能寄希望于保留旧集群作为回滚手段。
二、升级前的准备与兼容性检查
首先应确认当前PostgreSQL版本和目标版本都受pg_upgrade支持。较新的pg_upgrade可以处理多个旧版本,但过于古老的版本可能需要先做中间升级。实际生产环境一般会从较新的稳定版本直接升级到更高稳定版本,例如从PostgreSQL 14升级到16。升级前需要安装好目标版本的二进制程序,并确认pg_config或bin目录位置。
磁盘空间虽然不再是主要瓶颈,但初始化新集群、生成临时文件以及日志文件仍会消耗少量空间。更重要的是需要检查旧数据目录、新数据目录以及所有表空间目录是否位于同一文件系统。可以使用df -T查看挂载点和文件系统类型,确保它们一致。
在正式升级前,强烈建议先运行一次完整备份。pg_upgrade不会自动备份数据,一旦升级过程中发生不可恢复的错误,备份就是最后的防线。同时应安装新版本对应的扩展包,如果旧集群使用了PostGIS、pg_stat_statements等扩展,新版本环境中也必须提供兼容版本,否则升级后的数据库可能无法正常加载这些扩展。
以下命令可用来确认旧集群状态以及目录文件系统信息。
# 查看旧集群数据目录所在文件系统 df -T /pgdata/old # 查看新集群数据目录所在文件系统 df -T /pgdata/new # 确认旧集群版本 /usr/pgsql-14/bin/psql -c "SELECT version();"
三、完整升级步骤
第一步是停止旧版本实例。建议使用快速模式停止,确保所有连接断开并且数据已经落盘。例如执行pg_ctl stop -D /pgdata/old -m fast。停库后不要再次启动旧集群,否则可能破坏升级前的状态一致性。
第二步是用目标版本的initdb初始化一个新数据目录。新目录需要为空或不存在,由initdb自动创建。可以根据实际环境指定字符集和区域参数,但升级前的检查会检测新旧集群的编码设置是否兼容。初始化完成后,可以把旧配置文件复制到新目录中以作参考,但其中的某些参数可能需要根据新版本调整。
# 使用新版本initdb初始化数据目录 /usr/pgsql-16/bin/initdb -D /pgdata/new -E UTF8 --locale=C # 复制旧配置文件作为参考 cp /pgdata/old/postgresql.conf /pgdata/new/postgresql.conf.old cp /pgdata/old/pg_hba.conf /pgdata/new/pg_hba.conf.old
第三步是运行升级前检查。检查模式会执行一系列验证,例如版本是否支持、目录是否可访问、扩展是否可用、文件系统是否允许硬链接等,但不会实际修改任何数据。只有检查全部通过后,才应继续执行正式升级。注意--check和--link需要同时使用,这样检查过程才会按照硬链接模式的要求进行验证。
# 执行升级前检查 /usr/pgsql-16/bin/pg_upgrade \ --old-bindir /usr/pgsql-14/bin \ --new-bindir /usr/pgsql-16/bin \ --old-datadir /pgdata/old \ --new-datadir /pgdata/new \ --link \ --check
第四步是执行真正的升级。去掉--check参数后再次运行同样的命令。pg_upgrade会创建硬链接、升级系统目录并生成必要的辅助脚本。整个过程的耗时主要取决于数据库数量和系统元数据规模,而不是数据文件大小。升级完成后,pg_upgrade会在当前目录或指定输出目录生成analyze_new_cluster.sh等脚本。
# 正式执行硬链接模式升级 /usr/pgsql-16/bin/pg_upgrade \ --old-bindir /usr/pgsql-14/bin \ --new-bindir /usr/pgsql-16/bin \ --old-datadir /pgdata/old \ --new-datadir /pgdata/new \ --link
第五步是调整配置并启动新集群。需要根据新版本修改postgresql.conf中的参数,例如shared_preload_libraries、port、unix_socket_directories等。确认无误后启动新实例,并登录检查版本号。升级完成后应及时运行统计信息收集脚本,避免因旧的统计信息不准确导致执行计划异常。
# 启动新集群 /usr/pgsql-16/bin/pg_ctl start -D /pgdata/new # 确认新版本 /usr/pgsql-16/bin/psql -d postgres -c "SELECT version();" # 收集统计信息 /usr/pgsql-16/bin/vacuumdb --all --analyze-in-stages
四、风险控制与回滚策略
硬链接模式最大的风险在于共享数据文件。新集群一旦启动并发生写入,旧集群的数据页就会被修改,旧版本不再能够安全启动。因此不能把旧数据目录当作可随时回滚的备份,这一点必须提前与业务方和运维团队沟通清楚。
如果升级流程在启动新集群之前失败,通常可以删除新数据目录后重新初始化,再从备份或旧目录重新执行升级。但一旦新集群已经写入过数据,回滚就只能依赖升级前的物理备份或逻辑导出。建议在升级前使用pg_basebackup或文件系统快照制作完整备份,并在测试环境完整演练一次升级和回滚操作。
另一个常被忽略的问题是表空间。如果旧集群使用了多个表空间,而这些表空间位于不同文件系统,硬链接模式会直接报错。此时可以将相关表空间迁移到同一文件系统,或者放弃--link改用普通复制模式。普通复制模式虽然较慢,但旧集群仍然可以在升级失败后继续使用,因为数据文件是完整复制而非共享。
五、常见问题与排查
最常见的报错是硬链接创建失败,错误信息中可能出现Invalid cross-device link。这表示旧数据目录和新数据目录不在同一文件系统上。使用df -T检查所有涉及目录,必要时调整新数据目录位置,或者将表空间链接到相同文件系统后再升级。
权限问题也经常导致失败。pg_upgrade必须以运行数据库服务的系统用户身份执行,通常是postgres。如果使用root执行,可能会因为新数据目录或旧数据目录的属主不正确而报错。确保两个数据目录以及pg_upgrade输出目录对该用户可读写。
扩展不兼容会在检查阶段暴露出来。报错会指出某个扩展在新版本中不可用或版本不匹配。解决方法是先在目标版本的扩展目录中安装对应扩展,再重新执行检查。如果某些扩展已经不再维护,需要在升级前从旧库中删除这些扩展及其依赖对象。
升级后如果发现数据库性能下降,不必立即怀疑升级过程。常见原因是统计信息过期,运行vacuumdb --all --analyze-in-stages后多数查询计划能够恢复正常。如果仍有异常,可通过pg_stat_statements定位具体SQL,而不是直接回滚。
pg_upgrade硬链接模式PostgreSQL升级修改时间:2026-08-24 01:09:39