PostgreSQL 14 停止支持倒计时:14 → 18 大版本升级的完整实操清单
一句话结论: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 当前 minor | 18.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) |
|---|---|---|---|---|
| 18 | 18.6 | 是 | 2025-09-25 | 2030-11-14 |
| 17 | 17.11 | 是 | 2024-09-26 | 2029-11-08 |
| 16 | 16.15 | 是 | 2023-09-14 | 2028-11-09 |
| 15 | 15.19 | 是 | 2022-10-13 | 2027-11-11 |
| 14 | 14.24 | 是(将于 2026-11-12 终结) | 2021-09-30 | 2026-11-12 |
| 13 | 13.23 | 否 | 2020-09-24 | 2025-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 | 硬链接复用数据块 | 启动新库后即不可用 | 新旧数据目录同一文件系统 |
| 克隆 | --clone | reflink / 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],工程上必须记住这三条:
- 用了
--check:旧库未被修改,可直接启动。 - 用了
--copy或--clone:旧库未被修改,可直接启动。 - 用了
--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/postgresqlwhich will then place PostgreSQL data in a subdirectory, allowing usage ofpg_upgrade --linkwithout 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-checksums | 0 成本,但放弃页校验能力 | 仅在旧库确实不能停机时 |
实测选择了 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-only | 0.19 s |
vacuumdb --all --analyze-only | 1.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.html | shared_buffers 默认 128MB、25% 起始、>40% 通常无益;effective_io_concurrency 默认 16 |
核验说明:本文所有版本号、日期、参数名与命令语义均逐条比对上述官方页面(2026-11-09 抓取)。
实测分两部分,均于 2026-11-09 在本机执行:
- 第 6 节(容器化补充):
pg_upgrade --help/vacuumdb --help输出、镜像挂载约定报错、docker rm -f强杀后的PANIC: could not locate a valid checkpoint record(复现两次),均为实测原文。 - 第 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]而非实测。