这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 分区表把逻辑大表拆成多个物理子表,目标是提升可管理性、剪枝性能和维护效率,不是所有大表都适合分区。
13.1 分区类型
CREATE TABLE events (
id bigint,
event_date date NOT NULL
) PARTITION BY RANGE (event_date);
| 类型 | 适合 |
|---|---|
| RANGE | 时间、序号区间 |
| LIST | 租户、地区、枚举 |
| HASH | 均匀打散 |
| 多级分区 | 时间 + 租户等 |
13.2 创建分区
CREATE TABLE events_2026_08 PARTITION OF events
FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
默认分区:
CREATE TABLE events_default PARTITION OF events DEFAULT;
默认分区会承接无匹配数据,可能掩盖分区配置错误,使用要谨慎。
13.3 自动分区
PostgreSQL 原生不会自动创建所有未来分区。常见做法:
- 定时任务提前创建;
- 应用按模板创建;
- 使用扩展;
- 运维平台管理。
示例存储过程:
CREATE OR REPLACE FUNCTION create_event_partition(day date)
RETURNS void AS $$
BEGIN
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF events FOR VALUES FROM (%L) TO (%L)',
'events_' || to_char(day, 'YYYY_MM'),
date_trunc('month', day),
date_trunc('month', day) + interval '1 month'
);
END;
$$ LANGUAGE plpgsql;
13.4 分区剪枝
EXPLAIN
SELECT count(*)
FROM events
WHERE event_date >= '2026-08-01'
AND event_date < '2026-09-01';
运行时参数可能导致运行时剪枝,执行计划仍需查看实际分区数量。分区键上的函数包装会削弱剪枝。
13.5 索引
父表创建分区索引:
CREATE INDEX ON events(event_date, user_id);
会在各分区创建对应索引。也可以对单个分区维护索引。
13.6 主键与唯一约束
分区表唯一约束必须包含分区键:
ALTER TABLE events
ADD CONSTRAINT pk_events PRIMARY KEY (event_date, id);
如果业务唯一键不含时间,需要全局唯一方案,如 UUID、序列或应用约束。
13.7 分区维护
删除旧分区:
DROP TABLE events_2023_08;
摘除分区:
ALTER TABLE events DETACH PARTITION events_2023_08;
比大规模 DELETE 更高效,也更容易归档。
13.8 分区数量
过多分区会带来:
- 计划时间增加;
- 锁管理成本;
- 内存和元数据压力;
- autovacuum 调度压力。
建议按数据量、保留期和查询窗口选择粒度。常见日志表按月或按天,不要默认按小时。
13.9 什么时候分区
适合:
- 数据量大;
- 查询常按时间或租户过滤;
- 需要快速归档删除;
- 单表索引过大;
- 冷热存储管理。
不适合:
- 小表;
- 查询无分区条件;
- 分区后仍访问所有分区;
- 唯一约束难以调整。
本章小结
分区表的核心收益是分区剪枝和生命周期管理。设计时先确定分区键和粒度,保证高频查询能剪枝,并提前准备自动创建和归档流程。
思考题
- RANGE、LIST、HASH 如何选择?
- 分区表唯一约束有什么限制?
- 为什么要提前创建分区?
- 分区是否总能提升查询性能?
- DETACH 与 DELETE 有什么差异?