PostgreSQL分区表架构
PostgreSQL分区表通过声明式分区将大表拆分为多个物理子表,查询时通过分区裁剪只扫描相关分区,减少IO开销。相比MySQL分区在5.7版本后基本停滞的状态,PostgreSQL的分区功能在12-16版本持续增强,已支持范围分区、列表分区、哈希分区三种类型,且支持多级分区嵌套。
分区表的核心组件:父表仅存储元数据,实际数据写入子分区。分区键决定数据路由规则。每个子分区是独立的物理表,可单独创建索引、约束和统计信息。
范围分区:时间序列数据的首选
范围分区按分区键的值范围划分数据,最常见的应用是按时间分区日志表、订单表等持续增长的时间序列数据。
-- 创建父表
CREATE TABLE orders (
id BIGSERIAL,
order_no VARCHAR(32) NOT NULL,
customer_id BIGINT NOT NULL,
amount NUMERIC(12,2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- 创建月度分区
CREATE TABLE orders_202601 PARTITION OF orders
FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE orders_202602 PARTITION OF orders
FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE orders_202603 PARTITION OF orders
FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');
-- ...每月一个分区
-- 默认分区存放不匹配任何范围的数据
CREATE TABLE orders_default PARTITION OF orders DEFAULT;
-- 在父表创建索引,自动传播到所有子分区
CREATE INDEX idx_orders_customer ON orders (customer_id);
CREATE INDEX idx_orders_status_created ON orders (status, created_at);
分区键必须包含在主键中。上例中主键是(id, created_at)而非单独id,因为PostgreSQL要求分区键列必须是主键的子集。
在父表上创建索引会自动在所有现有子分区上创建相同索引,新添加的子分区也会自动继承该索引定义。这是PostgreSQL 11+的特性,大幅简化分区索引管理。
自动分区:pg_partman扩展
手动创建分区维护成本高,pg_partman扩展提供自动分区创建和过期分区清理:
-- 安装pg_partman
CREATE EXTENSION pg_partman;
-- 配置自动分区
SELECT partman.create_parent(
p_parent_table => 'public.orders',
p_control => 'created_at',
p_type => 'native',
p_interval => '1 month',
p_premake => 3 -- 预创建3个未来分区
);
-- 配置过期分区自动归档
UPDATE partman.part_config
SET retention = '12 months',
retention_keep_table = false
WHERE parent_table = 'public.orders';
-- 设置定时维护任务
SELECT partman.run_maintenance_proc();
p_premake=3表示预创建3个未来月份的分区,确保新数据到来时分区已就绪。retention=’12 months’配置12个月后自动drop过期分区,retention_keep_table=false表示直接删除而非detach保留。
哈希分区:均匀分布与水平扩展
哈希分区对分区键计算哈希值后取模分配到固定数量的分区,适合无法按范围划分但需要分散负载的场景。典型用例是按用户ID哈希分区,使数据均匀分布到各分区。
CREATE TABLE user_sessions (
id BIGSERIAL,
user_id BIGINT NOT NULL,
session_data JSONB,
created_at TIMESTAMPTZ DEFAULT now(),
PRIMARY KEY (id, user_id)
) PARTITION BY HASH (user_id);
-- 创建8个哈希分区
CREATE TABLE user_sessions_p0 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 0);
CREATE TABLE user_sessions_p1 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 1);
CREATE TABLE user_sessions_p2 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 2);
CREATE TABLE user_sessions_p3 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 3);
CREATE TABLE user_sessions_p4 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 4);
CREATE TABLE user_sessions_p5 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 5);
CREATE TABLE user_sessions_p6 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 6);
CREATE TABLE user_sessions_p7 PARTITION OF user_sessions
FOR VALUES WITH (MODULUS 8, REMAINDER 7);
MODULUS指定分区总数,REMAINDER指定该分区接受的哈希余数。哈希分区的分区数在创建后无法动态增加(除非重建),因此需要提前规划分区数量。PostgreSQL 16对哈希分区做了优化,分区裁剪效率提升约30%。
分区裁剪与查询性能对比
分区裁剪是分区表性能优势的核心机制。查询条件包含分区键时,优化器自动排除不匹配的分区,只扫描目标分区。
-- 范围分区裁剪:只扫描2026年1月分区
EXPLAIN SELECT * FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2026-02-01';
-- Seq Scan on orders_202601 (只扫描一个分区)
-- 哈希分区裁剪:只扫描user_id对应的哈希分区
EXPLAIN SELECT * FROM user_sessions
WHERE user_id = 12345;
-- Seq Scan on user_sessions_p5 (只扫描一个分区)
-- 不带分区键的查询扫描所有分区
EXPLAIN SELECT * FROM orders WHERE status = 'pending';
-- Append
-- Seq Scan on orders_202601
-- Seq Scan on orders_202602
-- Seq Scan on orders_202603
-- ... (扫描所有分区)
使用EXPLAIN查看执行计划,确认分区裁剪是否生效。如果查询条件不含分区键,优化器无法裁剪分区,将扫描所有子分区,性能可能比非分区表更差(因为多个分区表的元数据开销)。
范围分区与哈希分区的选型建议
两种分区方案的选择基于数据访问模式:
选择范围分区的情况:
– 数据有天然的时间维度(日志、订单、交易记录)
– 查询通常带时间范围条件(查某天/某月的数据)
– 需要按时间归档或清理旧数据(detach旧分区比DELETE高效得多)
– 数据量随时间线性增长
选择哈希分区的情况:
– 数据没有明确的时间维度或范围维度
– 需要按某列均匀分散写入负载(高并发写入场景)
– 查询通常按特定列过滤(如按user_id查询用户会话)
– 分区数量相对固定,不需要频繁创建新分区
多级分区:PostgreSQL支持多级分区嵌套,如先按时间范围分区,再在每个时间分区内按用户ID哈希分区。但多级分区会增加元数据管理复杂度,一般建议单层分区,当单层分区数据量仍然过大时再考虑多级。
分区维护操作
常用分区维护操作:
-- 分离分区(保留数据但不再属于父表)
ALTER TABLE orders DETACH PARTITION orders_202601;
-- 分离并保留索引
ALTER TABLE orders DETACH PARTITION orders_202601
CONCURRENTLY;
-- 附加已有表作为新分区
CREATE TABLE orders_202604 (LIKE orders INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
ALTER TABLE orders ATTACH PARTITION orders_202604
FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');
-- 将分区数据迁移到其他表空间
ALTER TABLE orders_202601 SET TABLESPACE slow_disk;
DETACH PARTITION CONCURRENTLY不会阻塞查询,适合生产环境在线操作。ATTACH PARTITION要求附加表的结构与父表兼容,PostgreSQL会自动验证数据是否符合分区范围约束。
将冷数据分区迁移到廉价存储(如HDD表空间),热数据分区保留在SSD上,是分区表配合存储分层优化的典型实践。通过ALTER TABLE SET TABLESPACE即可实现,对应用层完全透明。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/postgresql-fen-qu-biao-shi-zhan-fan-wei-fen-qu-yu-ha-xi-fen/