PostgreSQL 14 停更前升级 17 / 18:pg_upgrade 只用了 2 秒,升完不 ANALYZE 的那段时间查询慢了 5 倍
PostgreSQL 14 将于 2026-11-12 停止支持。我们在测试机上把自造的 0.8 GB PG 14.24 集群分别升到 17.11 和 18.6,各跑 3 轮:pg_upgrade 本身 --copy 2.4–2.6 s、--link 1.7–2.0 s、--swap 1.65–1.68 s;--check 先后报 reg*、aclitem(PG16 改了格式)和 PG18 默认开启数据校验和不一致。升到 17 后 pg_stats 为 0,同一条 JOIN 从 184 ms 变成 994 ms,跑完 vacuumdb --analyze-in-stages(0.72 s)回到 133 ms;升到 18 普通统计保留,但扩展统计丢失,--missing-stats-only 0.2 s 补回。附只读预检 SQL、一键复跑脚本和结果 CSV。
PostgreSQL 14.24 → 17.11 / 18.6 实测(测试库、自造数据,2026-10-31):大家做升级方案时盯着 pg_upgrade 的停机时间,我们测下来它是最不用担心的一步,0.8 GB 集群 1.7–2.6 秒。真正决定切换后会不会出事的是统计信息:PG17 升完 pg_stats 为 0,Q1 慢 5.4 倍、Q4 慢 8 倍,autovacuum 要等表上有写入才会去补;PG18 保留了普通统计,但 CREATE STATISTICS 建的扩展统计不保留,Q4 的行数估算从 399 掉到 8。我的判断:从 14 升级,目标选 18;停机窗口按「pg_upgrade + ANALYZE + 回归 SQL」三段算,ANALYZE 做完再放流量。
适用对象
- PostgreSQL 官方版本策略页:14 的最后一个版本日期是 2026-11-12,当前小版本 14.24;17 支持到 2029-11-08,18 支持到 2030-11-14。
- pg_upgrade 本身很快:0.8 GB 测试集群(数据在页缓存里),升 17 --copy 2.55–2.60 s、--link 1.80–1.95 s;升 18 --copy 2.39–2.55 s、--link 1.66–1.71 s、--swap 1.65–1.68 s,各 3 轮。
- --check 的报错是一层一层出来的:PG18 新集群用默认 initdb 时,0.06 s 就停在「old cluster does not use data checksums but the new one does」;加 --no-data-checksums 重建后,才看到 reg* 列和 aclitem 列两个阻断项(aclitem 内部格式在 PG16 改了)。
- PG17 的 pg_upgrade 不迁移优化器统计:升完 pg_stats 0 行,Q1(订单 JOIN 客户)中位数从 183.6 ms 变成 994 ms,status='done' 估 19445 行、实际 4850231 行;Q4(相关列)从 4.2 ms 变成 34.4 ms。vacuumdb --all --analyze-in-stages 0.72 s 后,Q1 133 ms、Q4 3.5 ms。
- autovacuum 不是看「有没有统计」,而是看「改了多少行」:开着 autovacuum(naptime 5s)空跑 75 秒,pg_stats 仍是 0;每张表改 100 行后,1–2 秒内就自动 analyze 了。写得少的表会一直没有统计。
- PG18 的 pg_upgrade 保留普通统计(Q1 计划和 PG14 相同),但扩展统计不保留:Q4 里 customers 的估算从 399 变成 8(实际 400)。vacuumdb --analyze-in-stages --missing-stats-only 0.2 s,只分析了有扩展统计的那张表,估算回到 399。
PG14 停更日期,以及 17 和 18 各能用到什么时候
下面的日期都取自 PostgreSQL 官网版本策略页(2026-10-31 核对)。社区对每个大版本支持 5 年,最后一个小版本发布后就不再修复。
本节判断
- 从 14 升到 15、16,下一次升级只往后推 1–2 年;升到 17 或 18,能多用 3–4 年。
- 停更不等于马上不能用,但之后发现的安全问题社区不再修,合规审计也会把它列为风险项。
| 大版本 | 当前小版本 | 首次发布 | 最后版本日期 |
|---|---|---|---|
| 14 | 14.24 | 2021-09-30 | 2026-11-12 |
| 15 | 15.19 | 2022-10-13 | 2027-11-11 |
| 16 | 16.15 | 2023-09-14 | 2028-11-09 |
| 17 | 17.11 | 2024-09-26 | 2029-11-08 |
| 18 | 18.6 | 2025-09-25 | 2030-11-14 |
pg_upgrade --check:报错是一层一层出来的
升 17:--check 用时 0.45 s,同时报出两个阻断项:「Checking for reg* data types in user tables」和「Checking for incompatible "aclitem" data type」,并说明 aclitem 的内部格式在 PostgreSQL 16 改了。具体是哪张表哪一列,写在 pg_upgrade_output.d 下的 tables_using_reg.txt 和 tables_using_aclitem.txt 里:legacy_app 库的 app_hooks.fn(regproc)和 app_acl.acl(aclitem[])。
升 18:新集群用默认参数 initdb,--check 0.06 s 就停了,只报一句「old cluster does not use data checksums but the new one does」。原因是 PG18 的 initdb 默认开启数据校验和,而老集群当年没开,pg_upgrade 要求两边一致。用 initdb --no-data-checksums 重建新集群后再 --check,才看到和升 17 一样的 reg* 和 aclitem 两个阻断项。
所以别指望 --check 一次列全所有问题:前面的检查失败,后面的不会执行。我们的做法是在源库上先跑一遍只读预检 SQL(下载区),把校验和、reg*、aclitem、扩展、扩展统计、复制槽、两阶段事务一次查清,再用新版本的 pg_upgrade --check 收尾。
本节判断
- reg* 列里存的是 OID,升级后 OID 不保证一致,所以 pg_upgrade 直接拒绝(regclass、regrole、regtype 除外)。常见处理是改成存名字的 text 列。
- 想在 PG18 上开校验和:要么先在老集群上用 pg_checksums 开启(需要停库、扫全部数据文件),要么新集群先不开、升级后再处理。具体选哪种要看停机窗口,本文没有测 pg_checksums 的耗时。
| 目标 | 新集群 initdb | --check 用时 | 结果 |
|---|---|---|---|
| 17.11 | 默认 | 0.45 s | reg* 列、aclitem 列两个阻断项 |
| 18.6 | 默认(校验和开) | 0.06 s | old cluster does not use data checksums but the new one does |
| 18.6 | --no-data-checksums | — | reg* 列、aclitem 列两个阻断项 |
| 17.11 / 18.6 | 同上,删掉 legacy_app 后 | — | 通过 |
pg_upgrade 本身:0.8 GB 集群 1.7–2.6 秒
每轮都从同一份停机状态的 PG14 副本开始,执行 sync 后计时 pg_upgrade 整条命令。数据量小、又在页缓存里,所以几种方式差距只有 1 秒左右;--copy 要把数据文件复制一遍,数据越大差距越大,--link 和 --swap 主要是元数据操作。
--link 的代价是回退:升级完成时,pg_upgrade 把老集群的 global/pg_control 改名为 pg_control.old,输出里写明「Because "link" mode was used, the old cluster cannot be safely started once the new cluster has been started」。我们试了一次,直接 pg_ctl start 老集群,报找不到 global/pg_control,起不来。用 --link 或 --swap,回滚方案就只能是升级前的备份或备库,不能指望老目录。
pg_upgrade 整条命令耗时(s,3 轮中位数,越低越好)
17 --copy
2.55–2.60
17 --link
1.80–1.95
18 --copy
2.39–2.55
18 --link
1.66–1.71
18 --swap
1.65–1.68
源库 PG 14.24,upg_lab 792 MB,测试机 8 vCPU,数据已进页缓存;只适合比较几种方式,不能直接换算你的停机时间。
升到 17:统计信息是空的,同一条 JOIN 从 184 ms 变成 994 ms
PG17 的 pg_upgrade 输出最后一段写着:「Optimizer statistics are not transferred by pg_upgrade」,并建议运行 vacuumdb --all --analyze-in-stages。启动新集群后查 pg_stats,public 下 0 行,pg_statistic_ext_data 也是 0,两张表的 reltuples 是 -1。
这时跑同一组查询(每条 3 次取中位数)。Q1 统计 vip 客户的已完成订单:PG14 上 Nested Loop,183.6 ms;PG17 无统计时 status='done' 估 19445 行、实际 4850231 行,计划变成 Bitmap Heap Scan + Hash Join,994 ms。Q3 按城市统计 10 月以来的订单:估 540129 行、实际 66210 行,从索引扫描变成并行全表扫描,166.7 ms。Q4 按 city + district 查:customers 估 1 行、实际每个并行进程 200 行,34.4 ms。只有 Q2(主键倒序取 50 条)没受影响。
跑 vacuumdb --all --analyze-in-stages,整条命令 0.72 s。它分三轮:先用统计目标 1 快速出一版(这一轮 33.6 ms),再用 10,最后用默认值。只跑完第一轮时,Q1 已经回到 146 ms,但估算还很粗(Q4 估 430、实际 10000);三轮跑完,Q1 133 ms、Q3 65.5 ms、Q4 3.5 ms,估算和 PG14 基本一致。
本节判断
- 升级后比升级前快的几项,不要理解为 PG17 比 PG14 快:两边缓存状态不同,本文不做版本性能对比。
- 下载区的一键脚本复跑结果:无统计时 Q1 894–968 ms,analyze 后 125–132 ms,方向一致。
| 查询 | PG 14.24 升级前 | PG 17.11 升级后无统计 | 第一轮 ANALYZE 后 | analyze-in-stages 跑完 |
|---|---|---|---|---|
| Q1 vip 已完成订单 JOIN | 183.6 | 994.1(5.4 倍) | 146.0 | 133.1 |
| Q2 最新 50 条待发货 | 0.63 | 0.65 | 0.66 | 0.59 |
| Q3 10 月以来按城市汇总 | 80.4 | 166.7 | 84.3 | 65.5 |
| Q4 city + district 相关列 | 4.2 | 34.4(8 倍) | 3.5 | 3.5 |
「autovacuum 会自己补统计」:要看表上有没有写入
很多升级方案里写着「升级后 autovacuum 会自动 analyze」。我们在刚升级的 PG17 上打开 autovacuum,把 autovacuum_naptime 调到 5 秒,什么都不写,等了 75 秒:n_mod_since_analyze 是 0,last_autoanalyze 为空,pg_stats 仍是 0 行。
然后在 customers 和 orders 各 UPDATE 100 行(12:01:39),1–2 秒内两张表都自动 analyze 了(12:01:40、12:01:41),pg_stats 变成 10 行。autovacuum 判断要不要 analyze,看的是上次 analyze 以来改了多少行,而不是有没有统计。
所以写入频繁的表,统计很快会被补上;维表、字典表、历史归档表、只在月底跑报表的表,可能很久都没有统计,正好是那些大 JOIN 和报表查询会用到的表。默认 naptime 是 1 分钟,就算是写入频繁的表,也有一段窗口在用错误的计划跑。
本节判断
- 我们的做法:新集群启动后先不放业务流量,跑完 vacuumdb 再放。0.8 GB 的库只要 0.72 s;大库按表数、列数和统计目标增长,要在测试环境用生产规模的数据量预演一次。
升到 18:普通统计保留了,扩展统计没有
PostgreSQL 18 的发布说明写明:pg_upgrade 会保留优化器统计,但扩展统计(CREATE STATISTICS)不保留;同时新增 pg_upgrade --no-statistics、--swap,以及 vacuumdb --missing-stats-only。
实测一致:用 --swap 升到 18.6 后,pg_stats 有 10 行(和升级前一样),pg_statistic_ext_data 是 0,reltuples 也保留了。Q1、Q3 的计划和估算与 PG14 相同(Q1 估 27716、实际 24262)。Q4 里 customers 按 city + district 过滤,估算从 399 掉到 8(实际 400),连接估算从 9975 掉到 200(实际 10000)。这次 Q4 碰巧还是同一个计划,耗时 3.9 ms;在我们之前那篇扩展统计的测试里,同样的低估让计划变成了 Nested Loop。
pg_upgrade 18 的输出建议先跑 vacuumdb --all --analyze-in-stages --missing-stats-only,再跑 vacuumdb --all --analyze-only。前一条用时 0.20 s,只 analyze 了有扩展统计的 customers(orders 的 last_analyze 仍为空),Q4 估算回到 399;后一条完整 analyze 0.46 s。
| 项目 | PG 17.11 | PG 18.6 |
|---|---|---|
| 普通列统计(pg_stats) | 不迁移,0 行 | 保留,10 行 |
| 扩展统计(pg_statistic_ext_data) | 0 | 0(定义还在,数据没了) |
| reltuples | -1 | 保留 |
| 推荐命令 | vacuumdb --all --analyze-in-stages(0.72 s) | --analyze-in-stages --missing-stats-only(0.20 s),再 --analyze-only(0.46 s) |
| 只补缺失统计的选项 | 无 | --missing-stats-only(需超级用户) |
从 14 升,选 18;停机窗口按三段算
目标版本:我们倾向直接上 18。支持期到 2030-11-14,比 17 多一年;更重要的是升级后普通统计还在,ANALYZE 之前那段「计划全错」的窗口小得多。代价是两个要提前处理的点:新集群 initdb 的校验和要和老集群一致,有扩展统计的表升级后要补一次 --missing-stats-only。
停机窗口:按「pg_upgrade + ANALYZE + 回归 SQL」三段估,而不是只看 pg_upgrade。第三段是把核心查询(报表、大 JOIN、按相关列过滤的查询)拿出来跑一遍 EXPLAIN,对比升级前保存的计划。
回滚:用 --link 或 --swap,就不能指望老目录还能起来,要在升级前确认最近一次备份能恢复(可以参考下面链接里的 PostgreSQL 备份恢复演练脚本),或者保留一个不参与升级的备库。
PG14 → 18 升级当天的顺序
- 01
1
只读预检
在每个库上跑下载区的预检 SQL:校验和、reg*、aclitem、扩展、扩展统计、复制槽、两阶段事务。每个业务库各跑一次。
- 02
2
--check
新集群 initdb 的校验和与老集群一致(老集群没开就加 --no-data-checksums),装好同样的扩展,跑 pg_upgrade --check 直到通过。
- 03
3
保存升级前计划
把核心查询的 EXPLAIN 结果存下来,后面对比用。
- 04
4
pg_upgrade
--link 或 --swap 前确认备份可恢复;本次 0.8 GB 集群 1.65–1.71 s。
- 05
5
补统计
PG18:vacuumdb --all --analyze-in-stages --missing-stats-only,再 --analyze-only;PG17:vacuumdb --all --analyze-in-stages。做完再放流量。
- 06
6
回归
重跑第 3 步的查询,对比计划和行数估算;再按需重建逻辑复制、检查扩展版本。
第 1、3、6 步都是只读操作,可以先在生产只读副本上做。
只读预检 SQL 查哪些东西
预检 SQL 每条语句都是 SELECT,在 PG 14.24 上测过:legacy_app 库查出 regproc 列和 aclitem[] 列各 1 个,upg_lab 库查出 1 个扩展统计 customers_city_district_stx。每个业务库各跑一次,把输出发给我们也可以。
一键脚本会在你指定的目录里新建 3 个集群,只监听本机 Unix socket,用默认 500 万行时约需 4 GB 磁盘,在我们的测试机上完整跑完 39 秒。只能在测试机上用。
本节判断
- 1 版本、data_checksums、编码与各库大小
- 2 用户表里的 reg* 类型列
- 3 用户表里的 aclitem 列
- 4 已安装扩展及版本
- 5 扩展统计定义(PG18 升级后需要补)
- 6 复制槽(源库低于 17 时逻辑复制槽不迁移)
- 7 未完成的两阶段事务
- 8 MD5 口令角色数(需超级用户)
- 9 最大的 20 张表
- 10 统计目标设置
免费 · 留邮箱领检查单
PostgreSQL 14 停更升级检查单(Excel 可直接打开)
- PG14 2026-11-12 停更,升 17 / 18 前后 19 项检查:只读预检、--check、回滚、补统计、回归
- 每项写清怎么查、通过标准、不处理会怎样(附测试库实测数字)
- 配合站上的只读预检 SQL 使用,每个业务库各跑一次
觉得有用?转给正在做这件事的同事或朋友
参考依据
以下来源用于确认市场趋势、政策背景和术语边界;具体落地方案仍以客户的数据范围、权限和交付目标为准。
常见问题
PostgreSQL 14 什么时候停止支持?
按 PostgreSQL 官网版本策略页,14 的最后一个版本日期是 2026-11-12,当前小版本是 14.24。17 支持到 2029-11-08,18 支持到 2030-11-14。
pg_upgrade 升级后为什么查询变慢?
PG17 及以前的 pg_upgrade 不迁移优化器统计,升级后 pg_stats 是空的,优化器按默认估算选计划。本次测试 Q1 从 183.6 ms 变成 994 ms,跑完 vacuumdb --all --analyze-in-stages(0.72 s)后回到 133 ms。
升级到 PostgreSQL 18 还需要 ANALYZE 吗?
需要,但少很多。PG18 的 pg_upgrade 保留普通统计,不保留扩展统计。pg_upgrade 18 建议先跑 vacuumdb --all --analyze-in-stages --missing-stats-only,再跑 --analyze-only。本次前者 0.20 s,Q4 的估算从 8 恢复到 399。
pg_upgrade 报 old cluster does not use data checksums but the new one does 怎么办?
PG18 的 initdb 默认开启数据校验和,老集群没开就会报这个错。用 initdb --no-data-checksums 重建新集群再升级,或先在老集群上开启校验和。
pg_upgrade 报 aclitem 或 reg* 数据类型怎么处理?
aclitem 内部格式在 PostgreSQL 16 改了,用户表里有 aclitem 列时不能从 15 及以下直接升级;reg* 列(regclass、regrole、regtype 除外)存的是 OID,也会被拒绝。具体表和列写在 pg_upgrade_output.d 的 tables_using_*.txt 里,通常改成 text 列后再升级。
用 --link 升级后还能回退到老版本吗?
新集群启动后不能。pg_upgrade 会把老集群的 global/pg_control 改名为 pg_control.old,本次直接启动老集群报找不到 pg_control。回滚要依赖升级前的备份或备库。
下一步
推荐动作
你提供预检 SQL 的输出(可脱敏)和核心查询清单,盈尺(Surinch)按校验和、阻断类型、扩展、扩展统计、复制槽和回滚方式给出升级前必须处理的项目、目标版本建议和当天的操作顺序。生产变更由你方授权执行。