CoreRanch
返回文章列表

PostgreSQL 14 停止支持倒计时:14 → 18 大版本升级的完整实操清单

2026-11-09 #数据库

一句话结论:2026-11-12 的季度更新发布之后,PostgreSQL 14 不再有任何补丁。仍跑在 14.x 的实例,唯一安全的终点版本是 14.24,升级目标应是 18.x(当前 18.6)——19 的 GA 还有 10 天,现在不是它的窗口。

0. 事实:三个日期决定你的排期

事实数值来源
PostgreSQL 14 首个版本2021-09-30[1]
PostgreSQL 14 当前 minor / 最终发布14.24 / 2026-11-12[1]
社区大版本支持年限首个版本后 5 年[1]
PostgreSQL 18 当前 minor18.6(18 首发 2025-09-25,2030-11-14 EOL)[1]
PostgreSQL 19 状态RC 1(2026-10-15 发布),GA 计划 2026-11-19;RC 2 与季度更新同日 2026-11-12[2]

这里有个容易被忽略的细节:11-12 那天同时发生三件事——季度更新集发布、PG 14.24 成为 14 分支的绝唱、PG 19 RC 2 发布。也就是说你没有任何"再等一个小版本看看"的余地。

支持的版本线,按官方表格整理:

大版本当前 minor是否仍受支持首发日期最终发布(EOL)
1818.6是2025-09-252030-11-14
1717.11是2024-09-262029-11-08
1616.15是2023-09-142028-11-09
1515.19是2022-10-132027-11-11
1414.24是(将于 2026-11-12 终结)2021-09-302026-11-12
1313.23否2020-09-242025-11-13

数据来源:[1],检索时间 2026-11-09。

1. 为什么是 18,而不是 19

19 处于 RC 阶段。RC 意味着"基本等同于首发版本",但仍会合入 11-12 更新集里的修复;官方明确 RC 2 与更新发布同日可用[2]。把生产库的第一跳放在 RC 上,等于把"修复未合入"的风险与"大版本格式变更"的风险叠加。

决策建议:

  • 14.x 实例 → 升到 18.6。14 与 18 之间隔着 15/16/17 三个版本,pg_upgrade 支持跨版本直跳(官方说明支持 9.2.X 及以后直接升到当前大版本[3]),不需要逐级。
  • 想等 19 的团队:把 19 的评估放在 12 月,先用 18 完成 EOL 硬截止,别把两件事耦合。
  • 15/16 实例:本轮不必动,但借这次机会把升级流水线跑通一遍,18 的 EOL 是 2030 年。

2. pg_upgrade 的四种文件搬迁模式:先选对再执行

pg_upgrade 的核心思路是不 dump/restore 用户数据,而是新建系统表、复用旧数据文件[3]。差异全部落在"数据文件怎么到新目录"这一件事上:

模式参数原理旧库是否仍可用硬性前提
复制--copy(默认)逐文件复制可用磁盘需容纳两份数据
硬链接--link硬链接复用数据块启动新库后即不可用新旧数据目录同一文件系统
克隆--clonereflink / copy_file_range可用Linux 4.5+ 且 Btrfs/XFS 建库时启用 reflink,或 macOS APFS
交换--swap直接搬目录 + 替换 catalog文件传输开始后即不可用同一文件系统

补充两个官方明确的行为:

  • --swap 在"关系数量很多"的库上可能比 --link/--clone/--copy/--copy-file-range 更快,但会在旧目录留下大量垃圾文件,拖慢 --sync-method=syncfs 的同步阶段,因此官方建议 --swap 配 --sync-method=fsync[3]。
  • --clone 是唯一"既快又能保留旧库可启动"的选项,代价是文件系统支持度。做升级演练时优先用它。

3. 可执行流程

以下命令按"生产可用"的顺序写,变量请按环境替换。

3.1 预检(不停机也能跑)

# 旧库 14.x,新库 18.6 的二进制已就位
export PGBINOLD=/usr/lib/postgresql/14/bin
export PGBINNEW=/usr/lib/postgresql/18/bin
export PGDATAOLD=/var/lib/postgresql/14/main
export PGDATANEW=/var/lib/postgresql/18/main

# 只在旧库运行时做检查:端口必须不同
$PGBINNEW/pg_upgrade --check \
  --old-bindir=$PGBINOLD --new-bindir=$PGBINNEW \
  --old-datadir=$PGDATAOLD --new-datadir=$PGDATANEW \
  --old-port=5432 --new-port=5433 \
  --clone

--check 会把需要人工处理的事项列出来,并且不改动任何数据;如果准备用 --link/--clone/--swap,必须把对应参数一起传给 --check,否则模式相关的检查不会执行[3]。

3.2 停机窗口内执行

# 1) 停旧库(流复制/日志传送的 standby 必须保持运行,以便接收所有变更)
pg_ctl -D $PGDATAOLD -m fast stop

# 2) 执行升级:--jobs 建议从 CPU 核数起
$PGBINNEW/pg_upgrade \
  --old-bindir=$PGBINOLD --new-bindir=$PGBINNEW \
  --old-datadir=$PGDATAOLD --new-datadir=$PGDATANEW \
  --clone --jobs=$(nproc) --retain

--retain 保留 SQL 与日志文件(默认成功后会删),第一次升级务必加:出错时 pg_upgrade_output.d/<ISO8601 时间戳>/ 下的内容就是唯一线索[3]。

3.3 统计信息重建(最常被跳过的一步)

pg_upgrade 默认会迁移大部分优化器统计(除非加 --no-statistics),但不会迁移 CREATE STATISTICS 创建的扩展统计、扩展自定义统计,以及累积统计系统收集的数据[3]。官方给出的两步法:

# 先给完全没有统计的关系快速生成最小统计
vacuumdb --all --analyze-in-stages --missing-stats-only --jobs=$(nproc)

# 再保证所有关系都有可用的累积统计(触发 autovacuum/analyze 判断)
vacuumdb --all --analyze-only --jobs=$(nproc)

如果 vacuum_cost_delay 非 0,可以临时压低以加速:

PGOPTIONS='-c vacuum_cost_delay=0' vacuumdb --all --analyze-only --jobs=$(nproc)

3.4 升级后立刻执行的验证

-- 版本与数据目录字符符号性(PG 18 起集群自带该设置)
SELECT version();
SHOW server_version_num;          -- 180006 表示 18.6

-- 统计信息新鲜度:last_analyze 为空说明还没重建
SELECT schemaname, relname, n_live_tup, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
WHERE last_analyze IS NULL AND last_autoanalyze IS NULL
ORDER BY n_live_tup DESC
LIMIT 20;

-- 失效对象检查:pg_upgrade 会在升级后生成重建脚本,跑完后这里应为空
SELECT count(*) AS invalid_indexes
FROM pg_index WHERE NOT indisvalid;

pg_upgrade 会为需要重建的表/索引自动生成脚本,官方明确:在重建脚本跑完之前访问被引用的表是不安全的,可能得到错误结果或糟糕的性能[3]。

4. 边界:这些对象升不过去

pg_upgrade 不支持包含以下 reg* OID 引用类型的表列[3]:

regcollation, regconfig, regdictionary, regnamespace,
regoper, regoperator, regproc, regprocedure

而 regclass、regrole、regtype 可以升级。上线前先扫一遍:

SELECT c.relnamespace::regnamespace AS schema,
       c.relname AS table_name,
       a.attname AS column_name,
       t.typname AS type_name
FROM pg_attribute a
JOIN pg_class c ON c.oid = a.attrelid
JOIN pg_type  t ON t.oid = a.atttypid
WHERE a.attnum > 0 AND NOT a.attisdropped
  AND t.typname IN ('regcollation','regconfig','regdictionary','regnamespace',
                    'regoper','regoperator','regproc','regprocedure')
  AND c.relkind IN ('r','p','m');

5. 回滚边界:什么时候还来得及

官方对回滚的描述是分模式、分阶段的[3],工程上必须记住这三条:

  1. 用了 --check:旧库未被修改,可直接启动。
  2. 用了 --copy 或 --clone:旧库未被修改,可直接启动。
  3. 用了 --link 且已经启动过新库:新旧数据文件共享,旧库已被写入,必须从备份恢复。仅"链接开始但未启动新库"的情况可以靠删掉 $PGDATA/global/pg_control.old 的后缀救回来。

所以窗口内的顺序应该是:升级 → 启动新库 → 立刻跑冒烟 SQL(SELECT 1、关键表 count、几个核心查询的 EXPLAIN)→ 确认无误后再让流量进来。不要在流量进入之后才发现问题。

6. 容器化环境的实测补充(2026-11-09)

如果你在 Docker / Kubernetes 里跑 PostgreSQL,第 3 节的流程有三个额外坑。以下都是在 PostgreSQL 18.6(postgres:18,Debian 18.6-1.pgdg13+2)上实测到的。

6.1 先确认二进制真的支持这些参数

不要假设"文档里有的参数,我装的就支持"。升级前直接问二进制:

$ pg_upgrade --help | grep -E 'clone|swap|link|check|jobs|sync-method'
 -c, --check                   check clusters only, don't change any data
 -j, --jobs=NUM                number of simultaneous processes or threads to use
 -k, --link                    link instead of copying files to new cluster
     --clone                   clone instead of copying files to new cluster
     --swap                    move data directories to new cluster
     --sync-method=METHOD      set method for syncing files to disk

$ vacuumdb --help | grep -E 'analyze-in-stages|missing-stats-only|analyze-only'
 -j, --jobs=NUM                  use this many concurrent connections to vacuum
     --missing-stats-only        only analyze relations with missing statistics
 -Z, --analyze-only              only update optimizer statistics; no vacuum
     --analyze-in-stages         only update optimizer statistics, in multiple

第 3.3 节那两条 vacuumdb 命令用的正是这些开关,实测存在于 18.6 的二进制中。

6.2 镜像的挂载约定变了,直接决定 --link 能不能用

postgres:18 官方镜像在检测到 /var/lib/postgresql/data 下有数据、但挂载布局不符合预期时,会拒绝启动并给出这样一段提示(原文摘要):

Counter to that, there appears to be PostgreSQL data in: /var/lib/postgresql/data (unused mount/volume) The suggested container configuration for 18+ is to place a single mount at /var/lib/postgresql which will then place PostgreSQL data in a subdirectory, allowing usage of pg_upgrade --link without mount point boundary issues.

这段话直接对应第 2 节的硬前提:--link/--clone/--swap 都要求新旧数据目录在同一个文件系统。如果你的旧库挂在 /var/lib/postgresql/data(旧约定)、新库挂在 /var/lib/postgresql(新约定),两者是不同挂载点,--link 会因为跨挂载点失败。升级前先统一挂载布局,而不是等 pg_upgrade 报错。

实测症状:容器状态停在 Created 或 Exited,docker logs 里就是上面这段文字,没有任何 PostgreSQL 日志。

6.3 停止方式错了,会得到一个起不来的集群

升级窗口里最常见的操作是"把旧容器删掉腾资源"。实测在 WSL2 + Docker volume 环境下用 docker rm -f 强杀一个刚跑完压测的实例,下一次启动直接 PANIC:

LOG:   database system was interrupted; last known up at 2026-11-09 14:47:29 UTC
LOG:   invalid resource manager ID in checkpoint record
PANIC: could not locate a valid checkpoint record at 0/10D3B708
LOG:   startup process (PID 32) was terminated by signal 6: Aborted
LOG:   shutting down due to startup process failure

这个状态无法自愈,只能重建或从备份恢复。同样的操作序列换成 docker stop(让 postmaster 正常关闭)后不再复现。

工程结论:容器化数据库的停止命令只有 docker stop(或 kubectl delete 给足 terminationGracePeriodSeconds),不要用 rm -f / kill -9。这条与第 5 节的回滚边界是同一件事的两面——你打算回滚,前提是旧集群还能启动。

说明:该 PANIC 在 WSL2 虚拟磁盘上复现了两次,是否在其他环境复现取决于存储栈的 fsync 语义,请勿据此推断"生产不会发生"。正确做法是永远不依赖强杀后的一致性。

7. 端到端实测:一次真实的 14.24 → 18.6 升级(2026-11-09)

第 3 节的流程我完整跑了一遍。过程中遇到两个会直接让你在窗口里失败的坑,都不是文档头几段会告诉你的。

7.1 环境

项值
基础镜像debian:bookworm + PGDG 官方源
旧版本PostgreSQL 14.24 (Debian 14.24-1.pgdg12+2)
新版本PostgreSQL 18.6 (Debian 18.6-1.pgdg12+2)
数据量799MB 库:pgbench -s 50(500 万行 accounts)+ t_orders 50 万行含索引 + pgcrypto 扩展
容器规格32 vCPU / 31.9 GiB,overlayfs 存储
执行时间2026-11-09 23:15 前后(Asia/Shanghai)

7.2 坑一:PG 18 的 initdb 默认开 data checksums,PG 14 默认关

装完两套二进制、灌完数据、停库,第一次 --check 直接失败:

Checking cluster versions                                     ok
old cluster does not use data checksums but the new one does
Failure, exiting

原因:PostgreSQL 18 的 initdb 默认启用数据页校验和,而 14 默认关闭,pg_upgrade 要求两边一致。

有两条互斥的解法,必须在排期时就定:

方案命令代价建议
A. 给旧库开启校验和pg_checksums --enable -D $PGDATA(需停机)实测 799MB 库 2806ms;大库按比例放大,且要全量重写页面推荐:校验和本身是你要的
B. 新库关掉校验和initdb --no-data-checksums0 成本,但放弃页校验能力仅在旧库确实不能停机时

实测选择了 A:

$ /usr/lib/postgresql/14/bin/pg_checksums --enable -D /srv/pg14
Checksums enabled in cluster

7.3 坑二:overlayfs 不支持 reflink,--clone 直接失败

解决校验和后,--check --clone(我原本最推荐的模式)报:

Checking for presence of required libraries                 ok
could not clone file between old and new data directories: Operation not supported
Failure, exiting

--clone 依赖文件系统 reflink(Btrfs/XFS reflink、macOS APFS),容器里的 overlayfs 不满足。同一份数据换 --link 检查立刻通过:

Checking for new cluster tablespace directories             ok
*Clusters are compatible*

结论:第 2 节那张表里的"克隆"在现代容器环境里经常不可用,上线前务必先用 --check --clone 试一次,失败就退到 --link(同文件系统即可)。

7.4 真实升级耗时

pg_upgrade -b .../14/bin -B .../18/bin -d /srv/pg14 -D /srv/pg18 \
           -p 5433 -P 5434 --link
阶段实测耗时
pg_checksums --enable(799MB)2.81 s
pg_upgrade --check --link< 1 s
pg_upgrade --link 实际升级5.11 s
vacuumdb --all --analyze-in-stages --missing-stats-only0.19 s
vacuumdb --all --analyze-only1.36 s
合计(不含停库/起库)≈ 9.5 s / 799MB

按这个比例,100GB 的库的"升级本体"在分钟级,真正的窗口成本在停库、备份校验、回滚预案上——这与第 8 节检查清单里把备份放在 T-1 天是一致的。

7.5 升级后校验(全部通过)

PostgreSQL 18.6 (Debian 18.6-1.pgdg12+2) on x86_64-pc-linux-gnu, ...
accounts=5000000          -- 与旧库一致
branches=50
orders=500000             -- 附加大表一致
invalid_idx=0             -- 无失效索引
missing_stats=0           -- 两条 vacuumdb 之后
Data page checksum version: 1   -- 新库校验和已启用

新库冒烟(pgbench -c 8 -j 4 -T 10):1239.6 tps,平均延迟 6.45 ms,失败事务 0。

7.6 两个文档细节被实测确认

① 回滚边界:pg_control.old 真的存在

--link 模式下、且已经启动过新库之后:

$ ls -l /srv/pg14/global/pg_control*
-rw------- 1 postgres postgres 8192 ... /srv/pg14/global/pg_control.old

旧库的 pg_control 被改名为 .old。这对应第 5 节的第 3 条:此时旧库的 pg_control 被移除、数据文件已与新库共享,回滚必须走备份恢复,不能靠改后缀(改后缀只在"链接开始但未启动新库"时有效)。

② 只生成 delete_old_cluster.sh,不再生成 analyze 脚本

升级完成时 pg_upgrade 打印的是:

Some statistics are not transferred by pg_upgrade.
Once you start the new server, consider running these two commands:
    /usr/lib/postgresql/18/bin/vacuumdb --all --analyze-in-stages --missing-stats-only
    /usr/lib/postgresql/18/bin/vacuumdb --all --analyze-only
Running this script will delete the old cluster's data files:
    ./delete_old_cluster.sh

实测目录里确实只有 delete_old_cluster.sh,没有 analyze_new_cluster.sh——所以第 3.3 节那两条 vacuumdb 命令必须手工执行,别等脚本。

8. 检查清单

阶段动作通过标准
T-7 天在克隆环境跑 --check + 全流程升级无 "fatal" 项,耗时在窗口内
T-3 天扫 reg* 列、扩展版本、自定义 FTS 词典结果为空或已计划改造
T-1 天全量备份 + 验证恢复恢复演练成功
窗口pg_upgrade --clone --jobs=N --retain无 --check 之外的告警
窗口内两条 vacuumdb完成无错
窗口内冒烟 SQL + pg_stat_statements 基线对比关键查询执行计划无退化
T+1 天删除旧目录(用升级结束时生成的脚本)磁盘回收

9. 一个常被问的问题:要不要顺便换掉 14 的老配置

要。14 到 18 之间 shared_buffers 的默认值没变(仍是 128MB,典型场景建议从内存 25% 起,超过 40% 通常不会比更小的值更好[4]),但异步 I/O 相关的一整套参数是 18 才有的,而 effective_io_concurrency 的默认值在 18 文档中是 16[4]。升级后如果不重新审视这几个参数,等于把新版本最值钱的部分关掉了。具体调法见同目录《PostgreSQL 18 异步 I/O 调优手册》。


附录:数据来源与检索时间

检索时间统一为 2026-11-09(Asia/Shanghai)。

编号来源URL本文引用的具体数据
[1]PostgreSQL 官方《Versioning Policy》(含各版本 EOL 表)https://www.postgresql.org/support/versioning/14.24 最终发布 2026-11-12;14 首发 2021-09-30;18 首发 2025-09-25、当前 minor 18.6、EOL 2030-11-14;17.11/16.15/15.19;13 已于 2025-11-13 EOL;5 年支持期
[2]PostgreSQL 官方新闻《PostgreSQL 19 RC 1 Released!》(2026-10-15)https://www.postgresql.org/about/news/postgresql-19-rc-1-released-3409/19 RC1 发布日 2026-10-15;RC2 与季度更新同日 2026-11-12;GA 计划 2026-11-19
[3]PostgreSQL 18 文档《pg_upgrade》https://www.postgresql.org/docs/18/pgupgrade.html支持 9.2+ 直跳;--check/--link/--clone/--swap/--copy-file-range/--jobs/--sync-method 语义;clone 需 Linux 4.5+ 与 Btrfs/XFS reflink 或 APFS;默认端口 50432;pg_upgrade_output.d 命名;统计迁移与 --no-statistics;reg* 不支持清单;回滚三条边界;两条 vacuumdb 命令
[4]PostgreSQL 18 文档《19.4 Resource Consumption》https://www.postgresql.org/docs/18/runtime-config-resource.htmlshared_buffers 默认 128MB、25% 起始、>40% 通常无益;effective_io_concurrency 默认 16

核验说明:本文所有版本号、日期、参数名与命令语义均逐条比对上述官方页面(2026-11-09 抓取)。

实测分两部分,均于 2026-11-09 在本机执行:

  1. 第 6 节(容器化补充):pg_upgrade --help / vacuumdb --help 输出、镜像挂载约定报错、docker rm -f 强杀后的 PANIC: could not locate a valid checkpoint record(复现两次),均为实测原文。
  2. 第 7 节(端到端升级):在 Debian bookworm + PGDG 容器中安装 14.24 与 18.6 两套二进制,对 799MB / 500 万行的库完整执行 pg_upgrade --check → --link → 启动新库 → 两条 vacuumdb → 冒烟压测。所有耗时(2.81s / 5.11s / 0.19s / 1.36s)、失败信息(old cluster does not use data checksums...、could not clone file...: Operation not supported)、校验结果(accounts=5000000、invalid_idx=0、missing_stats=0)均为真实输出。

仍然的局限:只有 799MB 数据、单机容器、overlayfs。升级耗时与数据量近似线性,但大库的真实窗口必须自己演练;另外本机未测"跨大版本 + 流复制 standby 的 rsync 升级"路径(第 3 节步骤 11),该路径的操作细节来自官方文档[3]而非实测。