跳至内容

14.1 PostgreSQL 扩展机制

CREATE EXTENSION vector; 只有一行,却横跨文件系统、权限系统、版本图和 数据库依赖。要治理扩展,必须先把这一行展开。

本节讨论 PostgreSQL 自己知道什么、不会替你知道什么。软件包仓库、容器与 Pigsty 映射留到后面。

14.1.1 control、SQL 脚本、动态库与对象所有权

一套扩展至少有两个身份

假设执行:

CREATE EXTENSION vector
WITH SCHEMA app_ext
VERSION '0.8.4';

这里的 vectorSQL 扩展名。它不是项目名 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;

这个关系有三个后果:

  1. 成员通常不能绕过扩展被单独删除;
  2. DROP EXTENSION 会删除成员,即使没有写 CASCADE
  3. 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 Extensionpg_extension

14.1.2 普通扩展、预加载库与超级用户需求

“已创建”不等于“已加载”

按运行方式可以粗分四类:

类型支持文件CREATE EXTENSIONpreload/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_cronpgaudit 等常见预加载场景,最终以目标扩展/版本文档和启动 实验为准。

superusertrusted 是两道判断

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 availablecontrol 文件不可见pg_available_extensions、server sharedir
could not access file $libdir/...动态库缺失/路径错pg_config --pkglibdir、各节点文件
must be superusercontrol 权限合同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 libraryserver 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.6

PostgreSQL 会按可用更新脚本寻找路径;路径不是任意版本号比较。若没有从 当前对象版本到目标版本的脚本链,更新就不能发生。所谓“降级”同样需要明确 反向脚本,不能假设 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.

少掉任何一段,扩展都只是“今天在这台主机上能用”,还不是可运营能力。


返回本章目录 · 下一节:内核、发行版与托管服务 · 查看全书目录 · 查看索引中心

最后更新于