18.2 PostgreSQL 的强项与代价
PostgreSQL 最容易被两种叙事误读:
“只是一个传统关系数据库”
“装上扩展就能替代所有数据系统”前者低估它,后者透支它。本节把收益与代价放在同一张账上。
18.2.1 关系一致性、可扩展类型与统一查询
关系模型把错误状态变成不可提交状态
应用校验常写成:
read current state
if valid:
write new state当有多个写入者、并发事务、批处理和人工运维时,这段逻辑很容易被绕过。 PostgreSQL 的独特价值不是“也能做校验”,而是把很多不变量放在最终提交 边界:
PRIMARY KEY
UNIQUE
NOT NULL
CHECK
FOREIGN KEY
EXCLUDE
transaction isolation
row and table locks不合法状态不只是“应用不建议写”,而是任何没有绕过权限边界的写入者都不能 提交。
第 4 章模型的关系校验和:
f8a7bfae59c6d16cd323abecfefe1014第 18 章每轮只读审计都会重新计算。它不是通用完整性证明,但把平台蓝图锚定 到一份确定的业务数据,而不是空白数据库。
一致性靠多个层次共同完成
“用事务”仍然太笼统。一个可靠模型通常组合:
| 层次 | 适合表达 |
|---|---|
| type/domain | 单值表示和基本范围 |
| column constraint | 必填、局部规则 |
row CHECK | 同一行字段关系 |
| unique/exclusion | 跨行唯一或不重叠 |
| foreign key | 引用存在与生命周期 |
| transaction | 多对象原子变更 |
| lock/isolation | 并发可见性与冲突 |
| function/trigger | SQL 约束难以表达的短原子规则 |
| application workflow | 远端调用、长流程、人机审批 |
越靠近数据的规则覆盖写入路径越广,但也越应短小、稳定、可解释。第 13 章
保留 accepted-with-scope,就是防止把业务编排全部塞进触发器。
类型不是列上的装饰
PostgreSQL 的类型参与:
storage representation
input/output validation
operator resolution
comparison and ordering
index operator class
planner statistics
function dispatch
wire protocol encoding所以 timestamptz、range、jsonb、vector 和 PostGIS geometry 不只是
不同的文本格式。类型、操作符和索引方法共同决定什么语义可以被查询与加速。
PostgreSQL 官方 Extending SQL 把数据类型、函数、聚合、操作符和索引操作符类都列为扩展点。这使一个扩展 能够进入规划器和执行器,而不只是作为应用旁边的黑盒服务。
第 16 章的:
ST_Intersects(...)
valid_during && ...
EXCLUDE USING gist (...)之所以能与普通关系条件组合,正是因为空间与 range 语义进入了 PostgreSQL 的类型和索引体系。
统一查询减少一致性缝隙
如果订单、商品、空间和搜索投影都在同一事务边界,应用可以:
SELECT ...
FROM order
JOIN product ...
WHERE tenant_id = $1
AND search_condition
AND spatial_condition;潜在收益:
- 一个快照;
- 一套权限;
- 一个查询计划;
- 一次网络往返;
- 一个可解释的事务边界;
- 少一条跨系统同步链;
- 少一套重试、对账和删除流程。
这不是说“一条大 SQL 总是最好”,而是跨系统拆分具有固定税:
serialization
network
partial failure
duplicate delivery
ordering
freshness
identity mapping
observability correlation
rebuild
deletion只有当拆分收益超过这组税,外置才有充分理由。
MVCC 让读写共存,但不消除冲突
PostgreSQL 的多版本并发控制让读者通常不阻塞普通写者,事务按快照看数据。 这使 OLTP、报表和维护可以在同一引擎协作。
但 MVCC 并不表示:
- 长事务没有代价;
- DDL 不需要重锁;
- 所有隔离级别结果相同;
- 副本读没有延迟;
- dead tuple 会自动即时消失;
- 写写冲突不需要处理。
统一引擎减少系统间一致性缝隙,内部仍要管理锁、快照、vacuum、序列化失败与 重试。这些成本在第 10、28、34 章分别展开。
统一系统目录让证据可查询
PostgreSQL 对象不是散落在配置文件中的传说。可以从 catalog 读取:
pg_class
pg_namespace
pg_proc
pg_type
pg_extension
pg_depend
pg_roles
pg_constraint
pg_index
pg_stat_*本章
extension-catalog.sql
固定扩展名、版本、schema、owner、relocatable 与 comment;
schema-catalog.sql
固定教学 schema 的 owner、marker 和对象计数。
这种自描述能力让平台可以做:
inventory
drift detection
privilege audit
upgrade preflight
backup/restore validation
extension ownership review但 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,它们共享:
CPU cores
shared buffer cache
OS page cache
memory address space
storage latency and bandwidth
WAL pipeline
checkpoint budget
background workers
connection slots
locks and snapshots
autovacuum workers
backup and replication bandwidth“查询彼此不锁”只覆盖其中一类竞争。
内存预算会按节点、worker 和并发放大
PostgreSQL 18 官方
Resource Consumption
说明 work_mem 是一个查询操作开始写临时文件前的基础内存限制;复杂查询可
同时有多个 sort/hash 操作,多个会话也会并行运行。
因此粗略风险模型不是:
[ memory = work_mem ]
而更接近:
[ memory_{work} \approx sessions \times operations \times workers \times effective_per_operation ]
它不是精确容量公式,但足以拒绝:
“这一条查询 32 MB 不 spill,
所以全局 work_mem 设成 32 MB。”第 17 章的单查询反例只证明两种执行路径,不授权全局参数变更。第 27 章必须 在真实并发预算下做单变量实验。
CPU 并行会把延迟问题变成吞吐问题
并行查询可缩短一个聚合,但 worker 不是免费核心:
one query × 3 processes
ten queries × 3 processes
autovacuum + backup compression + replication当机器已经饱和,更多并行可能让每条查询和系统总吞吐都变差。平台要同时看:
- 单查询 latency;
- 总吞吐;
- runnable queue;
- worker 是否实际获得;
- 交易查询 tail latency;
- background maintenance 债务。
搜索与空间索引有写放大和生命周期
一个索引的成本不仅是磁盘:
insert/update CPU
WAL volume
cache footprint
vacuum work
backup size
replica replay
build/rebuild window
statistics
upgrade compatibilityGIN、GiST、HNSW、BRIN 与 B-tree 的目标和代价不同。给每个新查询“加个索引” 可能把读延迟转移成写入、恢复与维护事故。
服务目录因此按 extension bundle 准入,而不是允许每个租户自由安装任意扩展。
WAL 是统一持久性的收益,也是共享压力面
在一个 PostgreSQL cluster 内,许多变更进入同一 WAL/复制/归档链。收益是 恢复语义统一;代价是:
- 大批量索引构建可能推动 WAL;
- 分析汇总刷新可能影响 replica lag;
- 一个归档故障会积压整个 cluster;
- logical slot 可能阻止 WAL 回收;
- 恢复需要理解所有扩展对象。
把某能力外置也不会让代价消失,只会把它变成跨系统日志与对账。选择应比较 两边完整成本。
长快照会制造维护债务
慢报表在 primary 上运行时,即使是只读,也可能:
延长旧 tuple 可见需求
阻碍 vacuum 清理
增加表/索引膨胀
与 DDL 锁冲突
占用连接和内存
挤压 cacheoffline replica 可以隔离一部分 CPU/I/O 与连接,但 replica 上的长查询可能 与 WAL replay 冲突,导致查询取消或 replay 延迟。它是新的合同,不是免费 读扩容。
一个 cluster 的爆炸半径必须被命名
共享 cluster 中,以下事件可能影响多个 database:
server crash
shared_preload_libraries error
disk full
WAL/archive failure
OS/package upgrade
major version upgrade
superuser mistake
host/network failure
HA control-plane error
backup repository problemschema 隔离挡不住这些;database 隔离也挡不住 cluster 级失败。只有在信任、 性能、升级、恢复或故障影响需要时,才上升到 instance/cluster 隔离。
维护责任不会被“开源免费”消除
每一项能力都要有人承担:
version selection
security advisories
package availability
configuration
monitoring
capacity
backup/restore
replication
upgrade
incident response
deprecation
data export许可证成本为零,运行责任仍然存在。一个无人负责的扩展,比一个功能较少但 有人值班的基线更危险。
用资源账本评估组合
对每项 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不保证现有同名扩展就是期望对象。
因此:
available != installed
installed != configured
configured != validated
validated in dev != admitted in production
admitted != permanently supported扩展准入的九道门
| 门 | 需要回答 |
|---|---|
| 来源 | 包来自哪里,如何验证与更新? |
| 版本 | 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 会看到:
pg_trgm relocatable=true
vector relocatable=true
btree_gist relocatable=true
postgres_fdw relocatable=true
postgis relocatable=false
plpgsql relocatable=falseextrelocatable=true 只表示扩展控制文件允许改变其对象所在 schema。它不说明:
- data portable;
- binary cross-version compatible;
- replica package 已存在;
- upgrade 可回滚;
- 对象 owner 安全;
- 业务语义不变。
不要从一个 catalog 布尔值推导整个生命周期。
preload 失败可能阻止实例启动
一些扩展需要 shared_preload_libraries。Pigsty 4.4 的
Extension Config
说明可用 pg_libs 和 pg_parameters 声明 preload 与参数,并提醒 preload
库缺失或加载失败可阻止 PostgreSQL 启动,修改 preload 还需要重启。
这把扩展从 database 范围提升到 instance 范围:
一个数据库想用
-> 每个节点需要 package
-> instance startup config 改变
-> rolling restart / HA 行为
-> 整个 cluster 的故障风险所以多租户平台不能允许任意 database owner 自助改变 preload。
本书当前扩展账本
只读实验在 PostgreSQL 18.4 上固定:
| 扩展 | 版本 | 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,目标节点缺包,查询、启动或
恢复可能失败。
因此节点准入应核对:
OS/repository identity
PostgreSQL package/version
extension package/version
shared library presence
control and SQL update paths
preload order
catalog extversion第 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 控制组合,而不是逐扩展放任
本章服务目录定义:
core
search-accepted
vector-pilot
spatiotemporal-conditional
federation-laboffering 引用 bundle,bundle 有生命周期与 gate。这带来三个好处:
- 同一组相互依赖的 package/config 一起审阅;
- 服务等级明确允许什么;
- 升级与退出有完整影响面。
例如 pg-ha-standard 默认不允许 federation-lab,vector-pilot 只能走
exception;这比“机器上有包,所以谁都能 CREATE”更可治理。
反例:把实验 FDW 变成生产
第 17 章为了在本地 loopback 无密码访问,明确写了:
{
"password_required_false": true,
"production_permitted": false
}第 18 章的负例把 production_permitted 改成 true,validator 必须报:
E_FDW_LAB_ONLY这个反例传达一种重要写作纪律:实验里为了隔离机制而采用的简化,必须被机器 可见地阻止进入生产蓝图,而不是靠读者记得某段警告。
准入是可撤销决定
即使已经 accepted,也需要 review trigger:
PostgreSQL major/minor change
extension version or package source change
security advisory
workload/scale change
restore or upgrade rehearsal failure
incident
owner/support change
upstream abandonment生产就绪不是一次性勋章,而是一项持续有证据支持的状态。
上一节:从数据库产品到能力组合 · 返回本章目录 · 下一节:明确替代边界 · 查看全书目录 · 查看索引中心