2. PostgreSQL分区机制全景
PostgreSQL 的分区能力并不是一次性设计出来的,而是经历了从“继承 + 约束 + 触发器”的手工方案,到 10 版本引入声明式分区、再到 11~14 版本逐步补齐索引、默认分区、哈希分区、并行裁剪等能力的演进过程。理解这条演进线,是判断“某个项目该用哪种分区、能不能升级、升级后哪些行为会变”的前提。
一、两种分区机制的本质差异
继承式分区(inheritance-based partitioning)是 PostgreSQL 早期唯一可行的方案。它的做法是:先建一张父表,再建若干子表并 INHERITS 父表,然后为每张子表添加 CHECK 约束来限定取值范围,最后通过触发器或应用层逻辑把数据路由到正确的子表。父表本身通常不存数据,查询时依赖 constraint_exclusion 参数来跳过不满足条件的子表。
声明式分区(declarative partitioning)从 PostgreSQL 10 开始提供,语法上直接使用 PARTITION BY RANGE / LIST / HASH,由内核负责路由、裁剪和元数据管理。它把“分区键、分区边界、分区与父表的关系”变成系统目录中的一等公民,而不是靠用户自己维护的约束和触发器。
两者的关键差异可以归纳为以下几点:
- 路由方式:继承式依赖触发器函数或应用层判断;声明式由内核在 INSERT 时自动路由,无需额外代码。
- 裁剪依据:继承式依赖子表上的
CHECK约束与constraint_exclusion;声明式直接使用分区边界元数据,裁剪更可靠。 - 索引与约束:继承式父表上的索引不会自动覆盖子表,需要逐表创建;声明式在父表上建索引会级联到所有分区(11 版本起支持)。
- 默认分区:声明式支持
DEFAULT分区兜底;继承式没有内建默认分区概念,需要额外子表加触发器。 - 维护操作:声明式提供
ATTACH/DETACH PARTITION,可在线挂载或卸载分区;继承式只能靠ALTER TABLE ... INHERIT/NO INHERIT,且不保证数据边界正确。
二、分区裁剪原理
分区裁剪(partition pruning)是分区表性能收益的核心来源。它的目标是在生成执行计划阶段,就把与查询条件不可能匹配的分区排除掉,从而减少扫描的 Relation 数量。
裁剪发生在两个层面:
- 计划期裁剪(plan-time pruning):当 WHERE 条件中的分区键是常量或可在计划阶段求值的表达式时,优化器直接排除不匹配分区。例如
WHERE log_date >= '2024-03-01' AND log_date < '2024-04-01',若按月分区,则只有 2024-03 那个分区会进入计划。 - 执行期裁剪(run-time pruning):当分区键来自参数、子查询或 PREPARE 语句的占位符时,计划阶段无法确定值,PostgreSQL 会在执行时对每个可能的分区再做一次判断。11 版本引入 run-time pruning,12 版本进一步支持在 Append 节点下动态裁剪。
裁剪能否生效,取决于分区键是否出现在可下推的谓词中。如果查询写成 WHERE date_trunc('month', log_date) = '2024-03-01',由于分区键被函数包裹,优化器无法直接比较边界,裁剪就会失效。这是实际排障中最常见的“分区表没变快”的原因之一。
三、适用版本与限制条件
声明式分区的能力随版本逐步增强,选型时必须对照目标版本:
| 版本 | 关键能力 | 实践影响 |
|---|---|---|
| 10 | RANGE / LIST 声明式分区 | 无默认分区,无哈希分区,父表索引不级联 |
| 11 | HASH 分区、默认分区、父表索引级联、run-time pruning | 可做哈希均匀打散,可在线 ATTACH/DETACH |
| 12 | Append 下动态裁剪、分区表支持外键引用 | 参数化查询裁剪更彻底 |
| 13 | 逻辑复制支持分区表、BEFORE 行级触发器 | 分区表可参与逻辑复制链路 |
| 14 | DETACH CONCURRENTLY、REINDEX 分区 | 卸载分区不再长时间持锁 |
限制条件同样需要提前确认:
- 分区键必须是不可变表达式,且不能是
NULL之外的复杂类型组合(部分类型不支持作为分区键)。 - 唯一约束和主键必须包含全部分区键列,否则无法在父表上创建。
- 声明式分区表不支持某些继承特性,例如父表上的
CHECK约束不会自动下推到分区。 - 分区数量过多会显著增加计划时间,通常建议单表分区数控制在数百到数千量级,具体取决于查询模式与硬件。
- 继承式分区在 10 之后仍可使用,但新项目应优先声明式;已有继承式方案迁移时,需用
ATTACH PARTITION逐表转换并校验边界。
constraint_exclusion = partition。升级到 11 及以上后,应优先把继承式分区迁移为声明式,以换取更可靠的裁剪和更低的维护成本。
理解这两种机制的差异、裁剪的触发条件以及版本边界,才能在面对“分区表为什么没走裁剪”“升级后行为为什么变了”“该不该迁移”这类问题时,给出有依据的判断,而不是凭经验猜测。