本文导读
PostgreSQL 在 8 月 13 日一次修复 28 个安全漏洞和 110 多个 Bug。本文不逐条翻译 CVE,而是提供升级优先级、巡检 SQL、主备验证、回退条件和完整变更清单,DBA 可以直接用于生产升级准备。
8 月 13 日,PostgreSQL 一口气发布了 18.6、17.11、16.15、15.19 和 14.24。
这次更新修复了 28 个安全漏洞,还有 110 多个 Bug。消息出来以后,DBA 面临的问题不是“要不要下载补丁”,而是生产库什么时候升、先升哪些、怎么证明升级已经做完。
这几个问题不解决,公告看得再仔细,最后还是要从头写变更方案。
所以这篇文章不准备逐条翻译 CVE。我把官方公告中真正会影响生产变更的内容拆开,整理成一套升级前检查、升级顺序和升级后验收清单。文中的 SQL 可以直接执行,最后一张表也可以复制到变更单里。
先说结论:支持版本都应该尽快升级,但不要所有库一起冲。客户端工具也在本轮漏洞范围内,升级后还有 GIN、btree_gist、ltree 三类对象需要额外检查。PostgreSQL 14 则要多做一件事,把大版本迁移计划一起定下来。
先定优先级,哪些库应该排在前面

先将资产和漏洞条件对应起来,再安排测试、分批升级与升级后验收。
28 个漏洞看着挺吓人,但只按漏洞数量安排窗口没有意义。
官方披露的问题分布在核心服务、客户端、过程语言和扩展模块中。多项 CVSS v3.1 评分达到 8.8,涉及 pg_dump、psql、正则表达式、to_char、pg_stat_statements、fuzzystrmatch 等路径。逻辑解码、类型 USAGE 权限、行级安全缓存,以及 GSSAPI 配合 SSL 使用时的加密约束也有修复。
换到生产环境里,可以按下面的顺序排:
| 优先级 | 环境特征 | 建议 |
|---|---|---|
| P0 | 有公网或跨网入口、低权限用户较多、外部人员可提交 SQL | 完成验证后优先升级 |
| P1 | 使用逻辑复制、过程语言、受影响扩展,或大量使用 pg_dump、psql |
紧随 P0 安排 |
| P1 | 核心生产主备、监管或敏感数据环境 | 按业务窗口尽快分批升级 |
| P2 | 仅内网访问、账号严格受控、攻击条件较少 | 不免升级,可稍后排期 |
| 单独立项 | PostgreSQL 14 | 先升 14.24,同时启动大版本迁移 |
“只在内网运行”只能降低一部分风险,不能直接作为不升级的理由。备份服务器、运维终端、扩展和复制链路都可能进入本轮影响范围。
这里还有一个很容易漏的地方:客户端。
pg_dump 有堆缓冲区溢出问题,psql 也在漏洞清单中。只升级数据库服务器,不检查备份机、跳板机、容器镜像和运维电脑,风险并没有完全收掉。资产清单里必须同时记录服务端和客户端版本。
Linux 主机可以先查出命令来自哪里:
type -a psql pg_dump
psql --version
pg_dump --version
如果备份任务运行在容器中,还要检查实际使用的镜像,而不是只看宿主机。
升级前先跑这组检查
小版本升级本身并不复杂。官方说明不需要执行 pg_upgrade,也不用导出再导入,停止数据库、更新二进制文件、重新启动即可。
真正费时间的是升级前确认。
下面这组 SQL 建议在每套环境保存一份结果,升级后再跑一次做对比。
1. 记录版本与运行状态
SELECT version();
SELECT current_setting('server_version_num') AS server_version_num,
pg_postmaster_start_time() AS instance_start_time,
pg_is_in_recovery() AS is_standby;
2. 盘点已安装扩展
SELECT extname,
extversion
FROM pg_extension
ORDER BY extname;
重点关注 btree_gist、ltree、pg_stat_statements、fuzzystrmatch、pgcrypto,以及当前环境自行安装的第三方扩展。数据库二进制升级后,第三方扩展是否兼容,仍需按各自厂商或项目文档确认。
3. 盘点可登录账号
SELECT rolname,
rolsuper,
rolcreaterole,
rolcreatedb,
rolreplication,
rolbypassrls
FROM pg_roles
WHERE rolcanlogin
ORDER BY rolsuper DESC, rolname;
这不是为了临时删账号,而是确认哪些漏洞利用条件可能在当前环境成立。账号越多、权限边界越复杂,升级优先级越高。
4. 检查逻辑复制对象
SELECT slot_name,
slot_type,
plugin,
database,
active
FROM pg_replication_slots
ORDER BY slot_name;
SELECT subname,
subenabled,
subslotname
FROM pg_subscription
ORDER BY subname;
pg_subscription 的查看权限受限,建议使用具备相应权限的管理账号执行。没有逻辑复制时,结果为空即可。
5. 保存主库复制基线
SELECT application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication
ORDER BY application_name;
这些结果升级后还要再查。不要只截图一个“streaming”,LSN 和延迟一起留着更好对比。
三类对象必须单独检查

三类对象都有明确触发条件,检查命中情况后再决定是否执行 ANALYZE 或 REINDEX。
本轮公告最特别的地方,是官方明确列出了三个升级后可能需要处理的问题。
GIN 索引:检查 reltuples
之前并行构建 GIN 索引时,Bug 可能把表在 pg_class 中的 reltuples 留成 Infinity 或 NaN。这个值异常后,autovacuum 和 autoanalyze 可能不再处理相应表,而且不会自己恢复。
升级后执行官方给出的 SQL:
SELECT DISTINCT
t.oid::regclass AS table_name,
t.reltuples
FROM pg_class t
JOIN pg_index i
ON t.oid = i.indrelid
JOIN pg_class ic
ON i.indexrelid = ic.oid
WHERE t.relhasindex
AND ic.relam = 2742
ORDER BY 1;
如果发现 reltuples 为 Infinity、NaN,或者与实际数据量明显不符,对相应表执行:
ANALYZE schema_name.table_name;
不要见到 GIN 索引就全部重建。官方建议是先检查 reltuples,发现异常后执行 ANALYZE,或者通过创建索引重置该值。
btree_gist:找出可能受影响的索引
btree_gist 涉及两类情况:float4、float8 列中可能存在 NaN,以及 bit、bit varying 列。
先确认扩展是否安装:
SELECT extname, extversion
FROM pg_extension
WHERE extname = 'btree_gist';
再列出建在这些数据类型上的 GiST 索引,作为人工核对范围:
SELECT DISTINCT
n.nspname AS schema_name,
idx.relname AS index_name,
tbl.relname AS table_name,
a.attname AS column_name,
format_type(a.atttypid, a.atttypmod) AS data_type,
pg_get_indexdef(idx.oid) AS index_def
FROM pg_index i
JOIN pg_class idx
ON idx.oid = i.indexrelid
JOIN pg_class tbl
ON tbl.oid = i.indrelid
JOIN pg_namespace n
ON n.oid = tbl.relnamespace
JOIN pg_am am
ON am.oid = idx.relam
JOIN pg_attribute a
ON a.attrelid = tbl.oid
AND a.attnum = ANY(i.indkey)
WHERE am.amname = 'gist'
AND a.atttypid IN (
'float4'::regtype,
'float8'::regtype,
'bit'::regtype,
'varbit'::regtype
)
ORDER BY 1, 2, 4;
这条 SQL 用于缩小检查范围,最终还要结合 index_def 确认索引是否使用 btree_gist 提供的操作符类。命中官方条件的索引,升级后执行:
REINDEX INDEX schema_name.index_name;
生产环境执行前要评估锁、额外空间和 I/O。是否改用 REINDEX CONCURRENTLY,需要结合 PostgreSQL 版本、索引类型和业务窗口决定,不能只为了减少阻塞就直接替换命令。
ltree:大多数库不会命中,但要确认
ltree 的条件比较极端。只有包含大约 14653 个以上标签的超长值,B-tree 索引才可能受到影响。
先找出 ltree 列上的 B-tree 索引:
SELECT DISTINCT
n.nspname AS schema_name,
tbl.relname AS table_name,
idx.relname AS index_name,
a.attname AS column_name,
pg_get_indexdef(idx.oid) AS index_def
FROM pg_index i
JOIN pg_class idx
ON idx.oid = i.indexrelid
JOIN pg_class tbl
ON tbl.oid = i.indrelid
JOIN pg_namespace n
ON n.oid = tbl.relnamespace
JOIN pg_am am
ON am.oid = idx.relam
JOIN pg_attribute a
ON a.attrelid = tbl.oid
AND a.attnum = ANY(i.indkey)
WHERE am.amname = 'btree'
AND a.atttypid = 'ltree'::regtype
ORDER BY 1, 2, 3;
对查询结果中的列检查最大标签数:
SELECT max(nlevel(column_name)) AS max_labels
FROM schema_name.table_name;
超过约 14653 时,升级后重建对应索引。没有安装 ltree 扩展的环境,类型转换会报不存在,可以直接跳过这一项。
主备环境怎么安排升级顺序
这轮更新还修复了一个影响 PostgreSQL 14、15 和 16 的回归问题。备库回放较旧小版本主库生成的 WAL 时,可能发生自死锁并停止推进。
因此,主备环境不能只验证数据库是否启动。
常见做法是先升级备库,再升级主库,可以缩短主库窗口,也方便先观察备库启动和回放情况。但具体顺序仍要看复制拓扑、包管理方式、自动故障转移组件和厂商要求。使用 Patroni、repmgr、云数据库或其他高可用平台时,先按对应产品的维护流程操作,不要把一套顺序硬套到所有环境。
升级后的主库检查:
SELECT application_name,
client_addr,
state,
sync_state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
write_lag,
flush_lag,
replay_lag
FROM pg_stat_replication
ORDER BY application_name;
备库检查:
SELECT pg_is_in_recovery() AS is_standby,
pg_last_wal_receive_lsn() AS receive_lsn,
pg_last_wal_replay_lsn() AS replay_lsn,
pg_last_xact_replay_timestamp() AS last_replay_time,
now() - pg_last_xact_replay_timestamp() AS replay_delay;
SELECT status,
sender_host,
sender_port,
slot_name,
latest_end_lsn,
latest_end_time
FROM pg_stat_wal_receiver;
如果备库没有新事务可回放,replay_delay 不能单独代表复制异常。最好在主库制造一条可控测试记录,确认对应 LSN 能够到达并完成回放。
升级后的验收,别停在 SELECT version()
数据库能启动只是第一关。
检查归档
SELECT archived_count,
failed_count,
last_archived_wal,
last_archived_time,
last_failed_wal,
last_failed_time,
stats_reset
FROM pg_stat_archiver;
检查 autovacuum 与统计信息
SELECT schemaname,
relname,
n_live_tup,
n_dead_tup,
last_autovacuum,
last_autoanalyze,
autovacuum_count,
autoanalyze_count
FROM pg_stat_user_tables
ORDER BY n_dead_tup DESC
LIMIT 30;
GIN 索引对应表要重点看,避免 reltuples 异常导致自动维护一直不执行。
检查无效索引
SELECT n.nspname AS schema_name,
c.relname AS index_name,
i.indisvalid,
i.indisready,
pg_get_indexdef(c.oid) AS index_def
FROM pg_index i
JOIN pg_class c
ON c.oid = i.indexrelid
JOIN pg_namespace n
ON n.oid = c.relnamespace
WHERE NOT i.indisvalid
OR NOT i.indisready
ORDER BY 1, 2;
检查长事务与阻塞
SELECT pid,
usename,
application_name,
state,
wait_event_type,
wait_event,
now() - xact_start AS xact_age,
left(query, 200) AS query
FROM pg_stat_activity
WHERE xact_start IS NOT NULL
ORDER BY xact_start;
最后再验证业务连接、连接池、监控采集、定时任务和备份。备份任务不能只看“任务启动成功”,至少确认产生了有效备份文件,并按现有恢复演练制度验证可恢复性。
什么时候应该停止变更或回退
回退条件最好在变更前写清楚,不能等出问题后现场讨论。
出现以下情况时,应暂停后续批次并按既定方案处理:
- 数据库或关键扩展无法正常加载;
- 主备复制无法恢复,WAL 长时间不再推进;
- 业务核心 SQL 出现可重复的错误结果或严重性能回退;
- 连接池、认证或应用连接持续异常;
- 归档、备份或监控链路无法恢复;
- 索引处理时间、锁等待或额外空间超过变更预估。
小版本更新通常只替换二进制文件,但回退不等于随便把旧包装回去。包管理依赖、扩展文件、启动脚本、参数变化和故障转移状态都要提前确认。使用厂商发行版或云平台的,回退流程以对应产品文档为准。
这张表可以直接放进变更单
升级前
- 服务端和客户端版本已经盘点,数据库、备份机、跳板机和容器均有记录。
- 扩展与逻辑复制对象已经盘点,受影响组件和第三方扩展兼容性已经确认。
- 主备、归档和备份基线已经保存,最近一次有效备份可以查到。
- GIN、
btree_gist、ltree候选对象已经找出,处理计划已经确定。 - 回退责任人、触发条件、操作命令和验证方法已经完成评审。
演练与生产升级
- 类生产环境已经完成升级,应用、复制、扩展、监控和备份验证通过。
- 生产环境按计划分批升级,前一批稳定后再进入下一批。
升级后
- 目标版本正确,数据库日志中没有新增严重错误。
- WAL 正常接收和回放,归档没有持续失败。
- 命中条件的特殊对象已经完成
ANALYZE或REINDEX。 - 连接池、定时任务、监控采集和备份任务均已恢复正常。
后续工作
- PostgreSQL 14 已经建立升级到受支持大版本的迁移计划。
PostgreSQL 14 将在 2026 年 11 月 12 日停止维护。14.24 解决的是眼前这轮安全问题,不能延长大版本的生命周期。还在使用 14 的团队,最好把“小版本补洞”和“大版本迁移”拆成两个任务,同时推进。
这轮更新要不要升?要,而且应该尽快进入计划。
本文小结
这轮 PostgreSQL 更新应该尽快安排。升级前同时盘点服务端、客户端、扩展和复制对象,保存运行基线并写清回退条件;升级后处理命中条件的 GIN、btree_gist 与 ltree 对象,再验证主备、归档、连接池、监控和备份。PostgreSQL 14 还要另外建立大版本迁移计划。
参考资料
版本号正确,只能说明补丁装上了。主备、归档、备份和业务链路全部恢复正常,这个升级窗口才算真正结束。




