18.2 PostgreSQL 的强项与代价
PostgreSQL 最容易被两种叙事误读:
前者低估它,后者透支它。本节把收益与代价放在同一张账上。
18.2.1 关系一致性、可扩展类型与统一查询
关系模型把错误状态变成不可提交状态
应用校验常写成:
当有多个写入者、并发事务、批处理和人工运维时,这段逻辑很容易被绕过。 PostgreSQL 的独特价值不是“也能做校验”,而是把很多不变量放在最终提交 边界:
不合法状态不只是“应用不建议写”,而是任何没有绕过权限边界的写入者都不能 提交。
第 4 章模型的关系校验和:
第 18 章每轮只读审计都会重新计算。它不是通用完整性证明,但把平台蓝图锚定 到一份确定的业务数据,而不是空白数据库。
一致性靠多个层次共同完成
“用事务”仍然太笼统。一个可靠模型通常组合:
| 层次 | 适合表达 |
|---|---|
| type/domain | 单值表示和基本范围 |
| column constraint | 必填、局部规则 |
row CHECK |
同一行字段关系 |
| unique/exclusion | 跨行唯一或不重叠 |
| foreign key | 引用存在与生命周期 |
| transaction | 多对象原子变更 |
| lock/isolation | 并发可见性与冲突 |
| function/trigger | SQL 约束难以表达的短原子规则 |
| application workflow | 远端调用、长流程、人机审批 |
越靠近数据的规则覆盖写入路径越广,但也越应短小、稳定、可解释。第 13 章
保留 accepted-with-scope,就是防止把业务编排全部塞进触发器。
类型不是列上的装饰
PostgreSQL 的类型参与:
所以 timestamptz、range、jsonb、vector 和 PostGIS geometry 不只是
不同的文本格式。类型、操作符和索引方法共同决定什么语义可以被查询与加速。
PostgreSQL 官方 Extending SQL 把数据类型、函数、聚合、操作符和索引操作符类都列为扩展点。这使一个扩展 能够进入规划器和执行器,而不只是作为应用旁边的黑盒服务。
第 16 章的:
之所以能与普通关系条件组合,正是因为空间与 range 语义进入了 PostgreSQL 的类型和索引体系。
统一查询减少一致性缝隙
如果订单、商品、空间和搜索投影都在同一事务边界,应用可以:
潜在收益:
- 一个快照;
- 一套权限;
- 一个查询计划;
- 一次网络往返;
- 一个可解释的事务边界;
- 少一条跨系统同步链;
- 少一套重试、对账和删除流程。
这不是说“一条大 SQL 总是最好”,而是跨系统拆分具有固定税:
只有当拆分收益超过这组税,外置才有充分理由。
MVCC 让读写共存,但不消除冲突
PostgreSQL 的多版本并发控制让读者通常不阻塞普通写者,事务按快照看数据。 这使 OLTP、报表和维护可以在同一引擎协作。
但 MVCC 并不表示:
- 长事务没有代价;
- DDL 不需要重锁;
- 所有隔离级别结果相同;
- 副本读没有延迟;
- dead tuple 会自动即时消失;
- 写写冲突不需要处理。
统一引擎减少系统间一致性缝隙,内部仍要管理锁、快照、vacuum、序列化失败与 重试。这些成本在第 10、28、34 章分别展开。
统一系统目录让证据可查询
PostgreSQL 对象不是散落在配置文件中的传说。可以从 catalog 读取:
本章
extension-catalog.sql
固定扩展名、版本、schema、owner、relocatable 与 comment;
schema-catalog.sql
固定教学 schema 的 owner、marker 和对象计数。
这种自描述能力让平台可以做:
但 catalog snapshot 只说明数据库内部状态;操作系统 package、共享库和各 副本节点仍需要主机层 inventory。
可移植性要按层讨论
“SQL 标准”不能概括可移植性。至少分:
| 层 | 可能的锁定 |
|---|---|
| schema/SQL | PostgreSQL 语法、函数、行为差异 |
| types | range、array、JSONB、vector、geometry |
| indexes | GIN/GiST/BRIN/opclass |
| routines | PL/pgSQL 与 trigger |
| extensions | binary/package/version |
| operations | backup、replication、HA、monitoring |
| semantics | collation、time zone、SRID、isolation |
有价值的 PostgreSQL 特性不必因为“可能锁定”就不用;应为高锁定能力写 export、 restore、替代与退出测试。锁定被管理,和假装不存在,是两种完全不同的架构。
18.2.2 通用性带来的资源竞争与维护责任
同一进程体系,共享多种稀缺资源
当交易、搜索、空间和分析都进入 PostgreSQL,它们共享:
“查询彼此不锁”只覆盖其中一类竞争。
内存预算会按节点、worker 和并发放大
PostgreSQL 18 官方
Resource Consumption
说明 work_mem 是一个查询操作开始写临时文件前的基础内存限制;复杂查询可
同时有多个 sort/hash 操作,多个会话也会并行运行。
因此粗略风险模型不是:
而更接近:
它不是精确容量公式,但足以拒绝:
第 17 章的单查询反例只证明两种执行路径,不授权全局参数变更。第 27 章必须 在真实并发预算下做单变量实验。
CPU 并行会把延迟问题变成吞吐问题
并行查询可缩短一个聚合,但 worker 不是免费核心:
当机器已经饱和,更多并行可能让每条查询和系统总吞吐都变差。平台要同时看:
- 单查询 latency;
- 总吞吐;
- runnable queue;
- worker 是否实际获得;
- 交易查询 tail latency;
- background maintenance 债务。
搜索与空间索引有写放大和生命周期
一个索引的成本不仅是磁盘:
GIN、GiST、HNSW、BRIN 与 B-tree 的目标和代价不同。给每个新查询“加个索引” 可能把读延迟转移成写入、恢复与维护事故。
服务目录因此按 extension bundle 准入,而不是允许每个租户自由安装任意扩展。
WAL 是统一持久性的收益,也是共享压力面
在一个 PostgreSQL cluster 内,许多变更进入同一 WAL/复制/归档链。收益是 恢复语义统一;代价是:
- 大批量索引构建可能推动 WAL;
- 分析汇总刷新可能影响 replica lag;
- 一个归档故障会积压整个 cluster;
- logical slot 可能阻止 WAL 回收;
- 恢复需要理解所有扩展对象。
把某能力外置也不会让代价消失,只会把它变成跨系统日志与对账。选择应比较 两边完整成本。
长快照会制造维护债务
慢报表在 primary 上运行时,即使是只读,也可能:
offline replica 可以隔离一部分 CPU/I/O 与连接,但 replica 上的长查询可能 与 WAL replay 冲突,导致查询取消或 replay 延迟。它是新的合同,不是免费 读扩容。
一个 cluster 的爆炸半径必须被命名
共享 cluster 中,以下事件可能影响多个 database:
schema 隔离挡不住这些;database 隔离也挡不住 cluster 级失败。只有在信任、 性能、升级、恢复或故障影响需要时,才上升到 instance/cluster 隔离。
维护责任不会被“开源免费”消除
每一项能力都要有人承担:
许可证成本为零,运行责任仍然存在。一个无人负责的扩展,比一个功能较少但 有人值班的基线更危险。
用资源账本评估组合
对每项 workload 建议记录:
| 项 | 峰值 | 隔离/配额 | 超限动作 |
|---|---|---|---|
| active connections | 待测 | pool + role limit | queue/reject |
| CPU | 待测 | service/host class | shed/defer |
| work memory | 待测 | role/session policy | spill/cancel |
| temp bytes | 待测 | temp_file_limit |
fail query |
| statement time | 待测 | workload timeout | cancel |
| storage growth | 待测 | forecast/alarm | expand/archive |
| WAL rate | 待测 | capacity/repository | throttle/fix |
| replica lag | 待测 | endpoint freshness | remove target |
| maintenance debt | 待测 | vacuum window | remediate |
未知值要写 unknown,然后由第 25–28 章补证据;不要填一个未经测量的舒服
数字。
18.2.3 扩展能力不自动等于生产就绪
CREATE EXTENSION 实际做了什么
PostgreSQL 18 官方
CREATE EXTENSION
说明,命令根据 control 与 SQL 脚本创建函数、类型、操作符、索引支持方法等
对象,并在 catalog 中记录它们的归属。
它还明确提醒:
- 支持文件必须先安装在 server;
- 某些扩展需要 superuser;
- 安装脚本本身属于信任边界;
- 不安全
search_path/可写 schema 可能带来风险; IF NOT EXISTS不保证现有同名扩展就是期望对象。
因此:
扩展准入的九道门
| 门 | 需要回答 |
|---|---|
| 来源 | 包来自哪里,如何验证与更新? |
| 版本 | PostgreSQL major/minor、扩展版本是否钉住? |
| 节点一致 | primary、replica、恢复目标都有相同 binary? |
| 安装安全 | trusted/superuser、schema、owner、search_path? |
| 配置 | 是否需要 preload、GUC、worker、restart? |
| 数据行为 | 类型、索引、collation、序列化语义是否固定? |
| 运行成本 | CPU、内存、WAL、vacuum、存储与构建窗口? |
| 恢复升级 | dump/physical restore/replica/PITR/major upgrade? |
| 退出 | 如何导出、降级、替代、删除? |
任一关键门为 unknown,生命周期就不能写 accepted。
relocatable 只回答 schema 迁移的一小部分
本章 catalog 会看到:
extrelocatable=true 只表示扩展控制文件允许改变其对象所在 schema。它不说明:
- data portable;
- binary cross-version compatible;
- replica package 已存在;
- upgrade 可回滚;
- 对象 owner 安全;
- 业务语义不变。
不要从一个 catalog 布尔值推导整个生命周期。
preload 失败可能阻止实例启动
一些扩展需要 shared_preload_libraries。Pigsty 4.5 的
Extension Config
说明可用 pg_libs 和 pg_parameters 声明 preload 与参数,并提醒 preload
库缺失或加载失败可阻止 PostgreSQL 启动,修改 preload 还需要重启。
这把扩展从 database 范围提升到 instance 范围:
所以多租户平台不能允许任意 database owner 自助改变 preload。
本书当前扩展账本
只读实验在 PostgreSQL 18.6 上固定:
| 扩展 | 版本 | schema | owner | 生命周期 |
|---|---|---|---|---|
pg_trgm |
1.6 | shop_ch14 |
pg36_owner |
accepted |
vector |
0.8.4 | shop_ch14 |
postgres |
pilot |
btree_gist |
1.8 | shop_ch16_ext |
pg36_owner |
conditional |
postgis |
3.6.4 | shop_ch16_ext |
postgres |
conditional |
postgres_fdw |
1.2 | shop_ch17_ext |
postgres |
lab-only |
plpgsql |
1.0 | pg_catalog |
postgres |
core |
版本表是 fixture 事实,不是对所有 PostgreSQL 18 环境的要求。平台应按自己的 软件仓库与升级策略重新验收。
owner 差异是需要解释的证据
为什么部分扩展 owner 是 postgres,部分是 pg36_owner?
- trusted 与非 trusted 安装权限不同;
- 扩展脚本可能创建需要高级权限的对象;
- extension object owner 与内部对象 owner 可能不同;
- dump/restore 与后续 upgrade 会使用这些身份。
本章只记录现状。第 23、30 章要决定生产 owner 模型,并证明升级和恢复不依赖 一个无人管理的超级用户流程。
物理复制不等于安装包复制
physical replica 会复制 data files 和 catalog 状态,不会替你把共享库包安装
到新主机。若 primary catalog 依赖某 .so,目标节点缺包,相关查询会失败;
若该库需要 preload,实例启动也会失败;逻辑恢复和升主后的业务验收同样无法
通过。
因此节点准入应核对:
第 19 章负责基线,第 30 章负责升级顺序。
备份成功不是扩展恢复成功
要验收扩展恢复,至少在隔离空目标中:
- 安装目标 PostgreSQL 与确切扩展 package;
- 恢复 base backup / WAL 或 logical dump;
- 验证
pg_extension、types、operators、indexes 与 dependencies; - 运行该扩展的业务 golden;
- 验证 replica 与应用权限;
- 保存 manifest 和校验和。
只看 pgBackRest 命令 exit 0,不能证明 PostGIS geometry、vector index 或 自定义 opclass 可用。
用 bundle 控制组合,而不是逐扩展放任
本章服务目录定义:
offering 引用 bundle,bundle 有生命周期与 gate。这带来三个好处:
- 同一组相互依赖的 package/config 一起审阅;
- 服务等级明确允许什么;
- 升级与退出有完整影响面。
例如 pg-ha-standard 默认不允许 federation-lab,vector-pilot 只能走
exception;这比“机器上有包,所以谁都能 CREATE”更可治理。
反例:把实验 FDW 变成生产
第 17 章为了在本地 loopback 无密码访问,明确写了:
第 18 章的负例把 production_permitted 改成 true,validator 必须报:
这个反例传达一种重要写作纪律:实验里为了隔离机制而采用的简化,必须被机器 可见地阻止进入生产蓝图,而不是靠读者记得某段警告。
准入是可撤销决定
即使已经 accepted,也需要 review trigger:
生产就绪不是一次性勋章,而是一项持续有证据支持的状态。
上一节:从数据库产品到能力组合 · 返回本章目录 · 下一节:明确替代边界 · 查看全书目录 · 查看索引中心