14.1 PostgreSQL 扩展机制
CREATE EXTENSION vector; 只有一行,却横跨文件系统、权限系统、版本图和
数据库依赖。要治理扩展,必须先把这一行展开。
本节讨论 PostgreSQL 自己知道什么、不会替你知道什么。软件包仓库、容器与 Pigsty 映射留到后面。
14.1.1 control、SQL 脚本、动态库与对象所有权
一套扩展至少有两个身份
假设执行:
CREATE EXTENSION vector
WITH SCHEMA app_ext
VERSION '0.8.4';这里的 vector 是 SQL 扩展名。它不是项目名 pgvector,也不必等于
RPM/DEB 包名。PostgreSQL 根据 server 自己的安装目录寻找:
$(pg_config --sharedir)/extension/vector.control
$(pg_config --sharedir)/extension/vector--0.8.4.sql
$(pg_config --pkglibdir)/vector.so # Linux 常见
$(pg_config --pkglibdir)/vector.dylib # macOS 可能出现典型扩展由三类文件组成:
| 文件 | 作用 | PostgreSQL 何时使用 |
|---|---|---|
name.control | 元数据、默认版本、权限、依赖、可迁移性 | 发现与创建/更新扩展 |
name--version.sql | 创建这一数据库版本的成员对象 | CREATE EXTENSION |
name--old--new.sql | 从一个对象版本迁到另一个版本 | ALTER EXTENSION UPDATE |
| 动态库 | C 函数、hook、访问方法等运行代码 | 创建时或 backend 加载/调用时 |
纯 SQL 扩展可以没有动态库;只提供可加载模块的组件也可能没有
CREATE EXTENSION 接口。不能从扩展名猜文件组合,要读 control 与目标版本
说明。
查看当前 server 的查找位置:
pg_config --sharedir
pg_config --pkglibdir
pg_config --version这里的 pg_config 必须属于目标 server major。用 PATH 中另一个 PostgreSQL
版本的 pg_config 检查文件,可能得到一个完全正确、却与正在运行实例无关
的目录。
control 文件是创建合同
一个简化 control 文件可能表达:
comment = 'example data type'
default_version = '1.4'
module_pathname = '$libdir/example'
relocatable = true
superuser = true
trusted = false
requires = 'btree_gist'关键字段:
default_version:未写VERSION时创建哪个对象版本;module_pathname:版本 SQL 中MODULE_PATHNAME的替换值,常指向$libdir下动态库;requires:必须先存在的其他扩展;superuser:安装脚本是否原则上要求超级用户;trusted:在superuser=true的前提下,是否允许具备当前数据库CREATE权限的非超级用户安装;relocatable:扩展整体是否允许换 schema;schema:若指定,强制成员安装到该 schema,并使扩展不可随意迁移。
这些是 具体已安装 control 文件 的事实,不是扩展项目永久不变的属性。 同名扩展升级后可以改变元数据,发行版也可能带不同补丁。用目录视图读取 当前 server 实际看到的值:
SELECT
name,
version,
installed,
superuser,
trusted,
relocatable,
schema,
requires
FROM pg_available_extension_versions
WHERE name IN ('pg_trgm', 'vector')
ORDER BY name, version;本章正式夹具观测到:
pg_trgm 1.3..1.6 superuser=t trusted=t relocatable=t
vector 0.8.4 superuser=t trusted=f relocatable=t这是 PostgreSQL 18.4 + 当前本地支持文件的证据;读者环境必须重查。
CREATE EXTENSION 创建的是一个依赖边界
版本 SQL 可以创建类型、函数、操作符、访问方法、opclass、表或其他对象。 PostgreSQL 把这些对象登记为扩展成员。扩展本身记录在:
SELECT
e.extname,
e.extversion,
pg_get_userbyid(e.extowner) AS owner,
n.nspname AS nominal_schema,
e.extrelocatable
FROM pg_extension AS e
JOIN pg_namespace AS n
ON n.oid = e.extnamespace;成员关系记录在 pg_depend,依赖类型为 e:
SELECT
d.classid::regclass AS member_catalog,
count(*) AS members
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'
WHERE e.extname = 'pg_trgm'
GROUP BY d.classid
ORDER BY member_catalog::text;这个关系有三个后果:
- 成员通常不能绕过扩展被单独删除;
DROP EXTENSION会删除成员,即使没有写CASCADE;pg_dump通常用CREATE EXTENSION重建整组对象,而不是逐个 dump 成员定义。
第三点非常重要:备份文件能记录“需要 vector 0.8.4 的对象合同”,却不会 把 control、SQL 脚本和动态库塞进备份。恢复目标必须先拥有兼容支持文件。
nominal schema 不是容器
pg_extension.extnamespace 常被叫作扩展 schema,但它不是一个不可穿透的
容器。官方文档明确指出,扩展成员可能位于多个 schema;这个字段表示扩展
的 nominal schema。扩展名本身也不受 schema 限定:
-- 一个数据库中只能有一个同名 extension
CREATE EXTENSION pg_trgm SCHEMA app_ext;
-- 这不是合法的第二份“另一个 schema 的 pg_trgm”
CREATE EXTENSION other.pg_trgm;relocatable=true 也不表示所有业务依赖都能无痛迁移。换 schema 会影响:
- 未全限定的函数与操作符解析;
- 固化在 view、expression index 或 function body 中的对象引用;
- 应用
search_path; - dump/restore 与旧迁移脚本;
- 安全审计假设。
把“目录允许 ALTER EXTENSION ... SET SCHEMA”与“应用兼容迁移”分开验证。
扩展 owner 与成员 owner
扩展有自己的 owner。通常创建者成为 extension owner;但安装脚本内部成员 的所有权还受脚本、可信安装规则和 PostgreSQL 版本语义影响,不能简单假设 “schema owner 拥有其中一切”。
本章把 pg_trgm 装在 shop_ch14:
SET ROLE pg36_owner;
CREATE EXTENSION pg_trgm
WITH SCHEMA shop_ch14
VERSION '1.3';
RESET ROLE;由于本地 control 标记 trusted=true,有数据库 CREATE 权限的
pg36_owner 可以安装,extension owner 是 pg36_owner。安装脚本在受控
的高权限上下文中执行,使扩展能够创建所需对象。
这不是把任意第三方 SQL 交给普通用户安全执行。trusted 是扩展供应者和
发行者作出的安全承诺,管理员仍要审查来源、版本与安装 schema。
参见 Packaging Related Objects into an Extension
和 pg_extension。
14.1.2 普通扩展、预加载库与超级用户需求
“已创建”不等于“已加载”
按运行方式可以粗分四类:
| 类型 | 支持文件 | CREATE EXTENSION | preload/restart |
|---|---|---|---|
| 纯 SQL 扩展 | control + SQL | 通常需要 | 通常不需要 |
| 按需加载的 C 扩展 | control + SQL + library | 通常需要 | 首次调用可加载 |
| 需要早期 hook 的扩展 | control + SQL + library | 通常需要 | 常需 shared preload |
| 非 SQL 插件 | library 或可执行组件 | 可能不需要 | 按子系统规则使用 |
扩展若要在 backend 初始化早期注册 shared memory、planner/executor hook、 background worker 或全局审计能力,往往需要:
shared_preload_libraries = '...'这个参数在 server 启动时处理。修改后 reload 不够,通常需要滚动重启或
集群重启。库名拼错、文件缺失或二进制不兼容,可能直接阻止实例启动,所以
它是比普通 CREATE EXTENSION 更高风险的变更。
检查声明与 live 值:
SELECT
name,
setting,
source,
sourcefile,
pending_restart
FROM pg_settings
WHERE name = 'shared_preload_libraries';只看配置仓库中的 YAML 不够;只看 SHOW 也不够。前者是意图,后者是当前
实例事实,还要在所有主备节点检查动态库。
pg_trgm 与本章的 vector 0.8.4 不需要 shared preload。不能由此推断
其他版本或扩展也不需要。Pigsty 当前文档列举 Citus、TimescaleDB、
pg_cron、pgaudit 等常见预加载场景,最终以目标扩展/版本文档和启动
实验为准。
superuser 与 trusted 是两道判断
pg_available_extension_versions 中:
superuser=true, trusted=false表示只有超级用户可以执行创建/更新脚本。vector 在本章环境属于这一类:
SET ROLE pg36_owner;
CREATE EXTENSION vector
WITH SCHEMA shop_ch14
VERSION '0.8.4';得到:
SQLSTATE 42501
permission denied to create extension "vector"
HINT: Must be superuser to create this extension.管理员安装后:
CREATE EXTENSION vector
WITH SCHEMA shop_ch14
VERSION '0.8.4';extension owner 保持管理员角色。应用只获得使用所需类型、函数和表权限, 不获得扩展所有权。
对:
superuser=true, trusted=true有数据库 CREATE 权限的非超级用户可以安装。安全关键点是:
- 安装脚本以 bootstrap superuser 的能力执行;
- extension owner 是调用者;
- 供应者必须保证非特权调用者不能借脚本选择、schema 或预置对象提权;
- 管理员应只信任随受控 PostgreSQL 发行版交付、且明确标记 trusted 的版本。
这就是为什么官方 CREATE EXTENSION 文档警告:从不可信来源安装扩展,相当
于以高权限运行其安装脚本。版本 SQL 能执行 DDL,也可能引用安装 schema
中预先存在的对象;安全安装应使用受控 schema 和受控 search_path。
权限分层
推荐把角色拆开:
platform/admin
├─ installs OS packages on every node
├─ changes preload and restarts
├─ creates untrusted extensions
└─ owns privileged extension lifecycle
NOLOGIN database owner
├─ owns application schemas/tables
├─ may own reviewed trusted extensions
└─ runs migrations through controlled SET ROLE
application login
├─ uses selected functions/operators/types
└─ cannot CREATE/ALTER/DROP EXTENSION本章证明:
-- pg36_app
ALTER EXTENSION pg_trgm UPDATE TO '1.6';返回:
SQLSTATE 42501
must be owner of extension pg_trgm应用能使用扩展能力,并不需要拥有生命周期控制权。
创建失败要按层定位
常见错误及第一检查点:
| 错误 | 更可能是哪一层 | 首查 |
|---|---|---|
| extension is not available | control 文件不可见 | pg_available_extensions、server sharedir |
could not access file $libdir/... | 动态库缺失/路径错 | pg_config --pkglibdir、各节点文件 |
| must be superuser | control 权限合同 | pg_available_extension_versions |
| must be loaded via shared_preload_libraries | 进程初始化条件 | pg_settings、重启状态、日志 |
| extension already exists | 数据库作用域冲突 | pg_extension |
| no installation script for version | 支持文件/版本图不完整 | available versions、包版本 |
| incompatible library | server major/CPU/ABI 错配 | 包构建、PG major、架构、启动日志 |
不要用反复 CREATE EXTENSION ... CASCADE 猜答案。先把错误归到供应、进程或
数据库层。
14.1.3 扩展依赖、版本与 ALTER EXTENSION
requires 是扩展依赖,不是 OS 包依赖
control 中:
requires = 'foo, bar'表示数据库内扩展依赖。创建前可以显式安装:
CREATE EXTENSION foo;
CREATE EXTENSION bar;
CREATE EXTENSION target;也可以:
CREATE EXTENSION target CASCADE;但生产治理不应默认 CASCADE,因为它会:
- 选择依赖的默认版本;
- 使用当前 schema/search path 决定安装位置;
- 扩大实际变更集合;
- 让审批只写一个扩展,实际多装若干对象。
更可审计的做法是显式列出依赖顺序、版本、schema、owner 和每步验证。
数据库依赖也不替代 OS 包依赖。目标 control 文件声明需要 foo,但节点上
仍必须先安装提供 foo.control、SQL 和 library 的软件包。
软件支持版本与对象版本分离
假设节点刚装入支持 1.6 的新包,而数据库仍显示:
SELECT extversion
FROM pg_extension
WHERE extname = 'pg_trgm';
-- 1.3此时:
filesystem supports: 1.3, 1.4, 1.5, 1.6
database objects are: 1.3这是正常的中间状态,不是 PostgreSQL 自动遗漏升级。装包不会主动在每个 数据库执行对象迁移;管理员必须逐库评审:
ALTER EXTENSION pg_trgm UPDATE TO '1.6';先列出版本:
SELECT
name,
version,
installed,
superuser,
trusted,
relocatable
FROM pg_available_extension_versions
WHERE name = 'pg_trgm'
ORDER BY string_to_array(version, '.')::integer[];再检查更新图:
SELECT source, target, path
FROM pg_extension_update_paths('pg_trgm')
WHERE source = '1.3'
OR target = '1.6'
ORDER BY source, target;本章得到:
1.3 -> 1.6 : 1.3--1.4--1.5--1.6PostgreSQL 会按可用更新脚本寻找路径;路径不是任意版本号比较。若没有从
当前对象版本到目标版本的脚本链,更新就不能发生。所谓“降级”同样需要明确
反向脚本,不能假设 ALTER EXTENSION ... TO old 会还原。
更新是 DDL 事务,不是无风险元数据改名
更新脚本可执行 DDL/DML,可能:
- 替换函数、操作符与类型支持函数;
- 增删成员;
- 改写扩展配置表;
- 获取对象锁;
- 使依赖表达式、索引或 cached plan 失效;
- 对大表触发长时间工作。
扩展脚本在一个隐式事务中运行,不能在其中自行提交,也不能把需事务外执行 的操作当普通更新步骤。即使脚本通常很快,也要把它当 schema migration:
freeze target package build
-> read release notes and update scripts
-> clone/restore rehearsal
-> dependency and lock inspection
-> behavior baseline
-> ALTER EXTENSION UPDATE
-> catalog + query + plan + log verification
-> observation window更新 owner 才能 ALTER EXTENSION;某些脚本还因 control 权限需要更高
特权。本章由 pg36_owner 更新自己拥有的 trusted pg_trgm,而不是给
应用角色临时提权。
成员变化与 dump 语义
扩展维护者可以用:
ALTER EXTENSION name ADD object;
ALTER EXTENSION name DROP object;调整成员关系。这不是业务迁移的日常捷径。成员一旦归入扩展:
- dump 通常不再单独保存其定义;
DROP EXTENSION会删除它;- 随手修改成员定义可能不会按预期进入 dump;
- 正确升级应通过新的扩展版本和 update script 交付。
检查成员,而不是只看 \dx:
SELECT
e.extname,
d.classid::regclass AS catalog,
count(*) AS members
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'
GROUP BY e.extname, d.classid
ORDER BY e.extname, catalog::text;本章 pg_trgm 1.3 有 37 个登记成员,更新到 1.6 后为 47;vector
0.8.4 在 PostgreSQL 18.4 夹具中有 237 个。成员数量用于发现本次环境漂移,
不是跨 PG major 的普适 golden。
本节结论
把 CREATE EXTENSION 读成一条完整声明:
using support files from this exact server installation,
run this reviewed version script with this privilege model,
create one database-scoped extension owned by this role,
attach these member objects and dependencies,
and promise that future dump/restore/update can obtain matching files.少掉任何一段,扩展都只是“今天在这台主机上能用”,还不是可运营能力。
返回本章目录 · 下一节:内核、发行版与托管服务 · 查看全书目录 · 查看索引中心