# 扩展与依赖升级

LLMS 索引： [llms.txt](/llms.txt)

---

PostgreSQL 本体升级成功，扩展仍可能让新 cluster 无法启动、schema restore 失败，或更
隐蔽地在旧类型/索引上执行新二进制代码。原因是扩展不是一个对象，而是一条跨越软件
仓库、文件系统、配置、系统目录和业务数据的依赖链。

## 30.3.1 二进制包、数据库对象和预加载顺序 {#item-30-3-1}

### 一项扩展至少有五层

| 层 | 示例 | 升级问题 |
|---|---|---|
| OS/package | `postgresql-18-postgis-*`、`.so` | 是否有目标 major/架构/OS 包 |
| control/SQL files | `.control`、`--1.0--1.1.sql` | default version 与 update path |
| preload/config | `shared_preload_libraries`、GUC | 新 server 能否启动、是否需 restart |
| database catalog | `pg_extension.extversion`、members | 每个 database 当前装了什么 |
| stored data/index | extension type、operator class、index | 新代码能否解释旧物理表示 |

`pg_extension` 只回答第四层：

```sql
SELECT e.extname,
       e.extversion,
       n.nspname AS schema,
       r.rolname AS owner,
       e.extrelocatable
FROM pg_extension AS e
JOIN pg_namespace AS n ON n.oid = e.extnamespace
JOIN pg_roles AS r ON r.oid = e.extowner
ORDER BY e.extname;
```

还要查询目标 server 实际能提供的版本：

```sql
SELECT name,
       default_version,
       installed_version,
       comment
FROM pg_available_extensions
ORDER BY name;

SELECT name, version, installed, superuser, trusted
FROM pg_available_extension_versions
ORDER BY name, version;
```

“源端安装了 3.4，目标仓库有 3.5”并不能证明可直接升级；中间 SQL update scripts
可能缺失，C ABI 也可能不支持目标 major。

### `pg_upgrade` 前先装文件，不要重建 SQL 对象

官方 `pg_upgrade` 流程要求先在新版本安装匹配的 extension shared libraries 与支持
文件。不要在空的新 cluster 预先执行：

```sql
CREATE EXTENSION postgis;
```

旧 cluster 的 extension catalog 与对象会随升级迁入，提前 `CREATE` 会重复。正确顺序
通常是：

```text
锁定 old extension inventory
  -> 准备 target-major packages on every future node
      -> 检查 control / SQL / shared library
          -> 配置必要 preload，但暂不让错误配置影响 managed cluster
              -> pg_upgrade --check
                  -> pg_upgrade
                      -> 按生成脚本和扩展文档 ALTER EXTENSION UPDATE
```

`pg_upgrade` 会检查它能发现的 required libraries，也会为可用 extension update 生成
脚本；但官方明确指出外部模块的所有 binary compatibility 无法由它完全检查。包含自定义
background worker、WAL resource manager、shared memory 或特殊 table access method
的扩展，必须按扩展自己的 major-upgrade 文档测试。

### preload 是启动依赖

盘点：

```sql
SELECT name, setting, source, pending_restart
FROM pg_settings
WHERE name IN (
  'shared_preload_libraries',
  'session_preload_libraries',
  'local_preload_libraries'
);
```

`shared_preload_libraries` 中任一 `.so` 缺失或与新 server ABI 不匹配，目标可能根本
起不来。先在隔离 cluster 加载：

```bash
postgres -D /isolated/new -C shared_preload_libraries
```

再实际启动并检查 log。不要在正式窗口里通过“先把所有 preload 清空”绕过；这样启动的
server 可能缺少审计、监控、时序、列存或业务所依赖的语义。

HA 集群中每个候选 primary/replica 都必须有同一套兼容库。只在当前 primary 安装，
failover 才会暴露缺包。

## 30.3.2 扩展升级脚本与不可降级路径 {#item-30-3-2}

### 先证明存在完整 update path

```sql
SELECT source, target, path
FROM pg_extension_update_paths('postgis')
WHERE source = (
  SELECT extversion
  FROM pg_extension
  WHERE extname = 'postgis'
)
ORDER BY target;
```

目标是看到从当前 installed version 到批准 target version 的路径。然后显式指定：

```sql
ALTER EXTENSION postgis UPDATE TO '3.x.y';
```

不带 `TO` 会使用 control file 的 default version；软件仓库下一次更新 default 后，
同一 runbook 可能得到不同结果。版本和包摘要都应固定。

`ALTER EXTENSION UPDATE` 执行扩展提供的 SQL scripts。脚本可能：

- 修改 type/function/operator 签名；
- 重写 extension-owned table；
- 替换 operator class 或索引支持函数；
- 迁移内部元数据；
- 删除旧对象；
- 要求先后执行专用 pre/post-upgrade procedure。

即使 SQL transaction 回滚成功，已更换的 `.so`、preload、外部文件和其他 database
状态也不一定一起回滚。更重要的是，许多扩展根本不提供 downgrade script。

### 降级能力要逐版本证明

对每个扩展写矩阵：

| 项 | 必答 |
|---|---|
| package downgrade | 仓库是否保留 old major + old extension package |
| SQL downgrade path | `pg_extension_update_paths` 是否存在反向 path |
| on-disk format | 新版本写入后旧 binary 是否还能读 |
| index rebuild | 哪些 index 必须重建，能否 concurrent |
| logical export | extension type 如何导出为 portable form |
| fallback | 回旧 cluster、restore，还是只能前滚 |

如果没有反向 path，发布决策应写：

```text
before ALTER EXTENSION: old cluster / backup can still be rollback source
after extension writes new format: rollback requires restore or logical transform
```

不要尝试把 `pg_extension.extversion` 手工改回旧字符串；它只改 catalog 声明，不会逆转
SQL objects、内部表或存储格式。

### 扩展脚本也是受信任代码

升级脚本常以 extension owner 或 superuser 权限执行，binary 还能在 server 进程内运行。
包来源、签名/摘要、供应链和发布说明应进入变更评审。对 source superuser 不可信的
cluster，`pg_upgrade`/restore 还可能在目标执行源端预先布置的代码；隔离环境与代码
审阅不是可选项。

## 30.3.3 从 ch14 ADR 获取退出与兼容信息 {#item-30-3-3}

第 14 章要求扩展准入时就记录 extension ADR。升级时不要重新从零猜用途，而应把 ADR
转成当前 inventory：

```yaml
extension: postgis
business_owner: geo-platform
technical_owner: dba
installed_databases: [maps, routing]
installed_version: 3.x
target_version: 3.y
postgresql_majors: [17, 18]
os_arch: ubuntu24-arm64
packages:
  old: ...
  new: ...
preload: false
stored_types: [geometry, geography]
indexes: [gist, spgist]
upgrade_path_evidence: ...
downgrade_path: none
portable_export: EWKB/EWKT
exit_cost: high
```

将 ADR 与现场 catalog 对账：

```text
ADR 有、catalog 无 -> 是否已退役但配置/包残留
catalog 有、ADR 无 -> 未治理依赖，升级门禁失败
version 不同 -> 漂移，先解释
preload 不同 -> 启动风险
package 无 target major -> 路径不可行
```

还要找出 extension members 与业务依赖：

```sql
SELECT e.extname,
       pg_describe_object(d.classid, d.objid, d.objsubid) AS member
FROM pg_extension AS e
JOIN pg_depend AS d
  ON d.refclassid = 'pg_extension'::regclass
 AND d.refobjid = e.oid
 AND d.deptype = 'e'
ORDER BY e.extname, member;
```

业务对象依赖 extension type/function/operator 的方向还需从 `pg_depend` 反查。只有列出
stored types、indexes、views、generated expressions 和 functions，才能知道升级失败
影响哪些对象。

### 退出策略在升级时兑现

ADR 若声明“可移除”，彩排应实际证明：

```text
导出为 core PostgreSQL / portable representation
  -> 在无该 extension 的目标恢复
      -> 比较业务结果
          -> 重建目标索引
              -> 回放增量或冻结切换
```

若做不到，就把 exit cost 和供应商/社区生命周期写进升级风险。一个已停止支持 PG18 的
关键扩展，可能决定整个数据库只能停留在 PG17，或必须先做一次应用层去依赖迁移。

本章正式实验只迁移内建 `plpgsql`，升级后才安装 `amcheck` 验证两个 B-tree。它故意
不声称覆盖任何第三方扩展；生产票据必须用第 14 章 ADR 和真实包矩阵补齐这块证据。

---

[上一节：三类大版本升级路径](../02/) · [返回本章目录](../) · [下一节：locale、collation 与索引风险](../04/) ·
[查看全书目录](/toc/) · [查看索引中心](/indexes/)
