第十一章:分区表
最后更新: 2024-01-01
作者: PostgreSQL Team
页面目录
第十一章:分区表
11.1 分区表概述
分区表将大表拆分为多个较小的表(分区),提升查询性能和管理效率。
┌─────────────────────────────────────────────────────────────┐
│ 分区表示例 │
├─────────────────────────────────────────────────────────────┤
│ orders (父表) │
│ │ │
│ ┌─────────────────┼─────────────────┐ │
│ ▼ ▼ ▼ │
│ orders_2024_q1 orders_2024_q2 orders_2024_q3 │
│ (1-3月) (4-6月) (7-9月) │
└─────────────────────────────────────────────────────────────┘
11.2 分区类型
11.2.1 范围分区 (Range Partitioning)
按数值或日期范围分区
-- 创建分区表
CREATE TABLE orders (
id SERIAL,
user_id INTEGER NOT NULL,
order_date DATE NOT NULL,
total DECIMAL(10, 2),
status VARCHAR(20)
) PARTITION BY RANGE (order_date);
-- 创建按月分区的子表
CREATE TABLE orders_2024_01 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE orders_2024_02 PARTITION OF orders
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
CREATE TABLE orders_2024_03 PARTITION OF orders
FOR VALUES FROM ('2024-03-01') TO ('2024-04-01');
-- 创建季度分区
CREATE TABLE orders_2024_q1 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
11.2.2 列表分区 (List Partitioning)
按离散值列表分区
-- 按地区分区
CREATE TABLE sales (
id SERIAL,
region VARCHAR(50),
amount DECIMAL(10, 2),
sale_date DATE
) PARTITION BY LIST (region);
CREATE TABLE sales_east PARTITION OF sales
FOR VALUES IN ('北京', '天津', '河北');
CREATE TABLE sales_west PARTITION OF sales
FOR VALUES IN ('新疆', '西藏', '青海', '甘肃');
CREATE TABLE sales_other PARTITION OF sales
FOR VALUES IN (DEFAULT);
11.2.3 哈希分区 (Hash Partitioning)
按哈希值均匀分布
-- 按用户 ID 哈希分区
CREATE TABLE user_events (
id SERIAL,
user_id INTEGER,
event_type VARCHAR(50),
event_data JSONB,
created_at TIMESTAMPTZ
) PARTITION BY HASH (user_id);
CREATE TABLE user_events_p0 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE user_events_p1 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE user_events_p2 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE user_events_p3 PARTITION OF user_events
FOR VALUES WITH (MODULUS 4, REMAINDER 3);
11.3 自动分区管理
11.3.1 自动创建月度分区
-- 创建分区管理函数
CREATE OR REPLACE FUNCTION create_monthly_partition(
table_name TEXT,
partition_date DATE
)
RETURNS VOID AS $$
DECLARE
partition_name TEXT;
start_date DATE;
end_date DATE;
BEGIN
start_date := DATE_TRUNC('month', partition_date);
end_date := start_date + INTERVAL '1 month';
partition_name := table_name || '_' || TO_CHAR(start_date, 'YYYY_MM');
-- 检查分区是否存在
IF NOT EXISTS (
SELECT 1 FROM pg_tables
WHERE schemaname = 'public'
AND tablename = partition_name
) THEN
EXECUTE format(
'CREATE TABLE %I PARTITION OF %I FOR VALUES FROM (%L) TO (%L)',
partition_name, table_name, start_date, end_date
);
RAISE NOTICE '创建分区: %', partition_name;
END IF;
END;
$$ LANGUAGE plpgsql;
-- 创建未来 3 个月的分区
SELECT create_monthly_partition('orders', CURRENT_DATE);
SELECT create_monthly_partition('orders', CURRENT_DATE + INTERVAL '1 month');
SELECT create_monthly_partition('orders', CURRENT_DATE + INTERVAL '2 months');
11.3.2 使用 pg_partman 管理分区
-- 安装 pg_partman 扩展
CREATE EXTENSION pg_partman;
-- 配置自动分区
SELECT partman.create_parent(
p_parent_table => 'public.orders',
p_control => 'order_date',
p_interval => 'monthly',
p_premake => 3
);
-- 配置自动维护
UPDATE partman.part_config
SET infinite_time_partitions = true
WHERE parent_table = 'public.orders';
11.4 分区表操作
11.4.1 插入数据
-- 直接插入,PostgreSQL 自动路由到正确分区
INSERT INTO orders (user_id, order_date, total, status)
VALUES (1, '2024-01-15', 199.99, 'completed');
INSERT INTO orders (user_id, order_date, total, status)
VALUES
(1, '2024-02-20', 299.99, 'pending'),
(2, '2024-03-10', 149.99, 'completed');
-- 从分区表插入
INSERT INTO orders
SELECT * FROM orders_archive
WHERE order_date >= '2024-01-01';
11.4.2 查询分区表
-- 查询自动利用分区裁剪
SELECT * FROM orders WHERE order_date BETWEEN '2024-01-01' AND '2024-01-31';
-- 查看查询计划(验证分区裁剪)
EXPLAIN SELECT * FROM orders WHERE order_date = '2024-01-15';
-- 查看分区信息
SELECT
parent.relname AS parent_table,
child.relname AS partition,
pg_get_expr(child.relpartbound, child.oid) AS partition_range
FROM pg_inherits
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid;
11.4.3 分区维护
-- 分离分区
ALTER TABLE orders DETACH PARTITION orders_2024_q1;
-- 将分区转为独立表
ALTER TABLE orders_2024_q1 SET WITHOUT OIDS;
ALTER TABLE orders_2024_q1 SET TABLESPACE fast_storage;
-- 附加分区
ALTER TABLE orders ATTACH PARTITION orders_archive_2023
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
-- 删除旧分区
DROP TABLE orders_2022_q1;
-- 重命名分区
ALTER TABLE orders_2024_01 RENAME TO orders_jan_2024;
11.5 分区索引
11.5.1 分区索引类型
-- 在分区表上创建索引(自动应用到所有分区)
CREATE INDEX idx_orders_date ON orders (order_date);
CREATE INDEX idx_orders_user_date ON orders (user_id, order_date);
-- 在特定分区上创建索引
CREATE INDEX idx_orders_2024_user ON orders_2024_01 (user_id);
-- 部分索引(分区特定)
CREATE INDEX idx_orders_pending ON orders_2024_01 (created_at)
WHERE status = 'pending';
11.5.2 唯一约束
-- 分区表上的唯一约束必须包含分区键
CREATE TABLE orders (
id SERIAL,
order_no VARCHAR(50),
user_id INTEGER,
order_date DATE
) PARTITION BY RANGE (order_date);
-- 正确:包含分区键
CREATE UNIQUE INDEX idx_orders_no_date ON orders (order_no, order_date);
-- 错误:缺少分区键
-- CREATE UNIQUE INDEX idx_orders_no ON orders (order_no); -- 会报错
11.6 分区性能优化
11.6.1 查询优化
-- 使用分区键过滤
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE order_date >= '2024-01-01'
AND order_date < '2024-02-01';
-- 跨分区查询
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE order_date >= '2024-01-01'
AND order_date < '2024-07-01';
-- 聚合查询(利用分区并行)
SELECT
DATE_TRUNC('month', order_date) AS month,
COUNT(*) AS order_count,
SUM(total) AS revenue
FROM orders
GROUP BY DATE_TRUNC('month', order_date)
ORDER BY month;
11.6.2 分区裁剪
-- 启用分区裁剪
SET enable_partition_pruning = on;
-- 查看裁剪信息
EXPLAIN SELECT * FROM orders WHERE order_date = '2024-03-15';
-- Output 应包含:
-- -> Seq Scan on orders_2024_03 orders (cost=...)
11.7 分区迁移
11.7.1 从普通表转为分区表
-- 1. 重命名原表
ALTER TABLE orders RENAME TO orders_old;
-- 2. 创建分区表
CREATE TABLE orders (
id SERIAL,
user_id INTEGER,
order_date DATE,
total DECIMAL(10, 2)
) PARTITION BY RANGE (order_date);
-- 3. 创建分区
CREATE TABLE orders_2023 PARTITION OF orders
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE orders_2024 PARTITION OF orders
FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- 4. 迁移数据
INSERT INTO orders SELECT * FROM orders_old;
-- 5. 删除旧表
DROP TABLE orders_old;
11.7.2 分区归档
-- 将历史分区分离并归档
ALTER TABLE orders DETACH PARTITION orders_2023;
-- 可选:将分区移到归档表空间
ALTER TABLE orders_2023 SET TABLESPACE archive_tablespace;
-- 如果需要,重新附加
ALTER TABLE orders ATTACH PARTITION orders_2023
FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
11.8 本章小结
分区表关键概念:
| 主题 | 说明 |
|---|---|
| 分区类型 | Range、List、Hash |
| 分区管理 | 创建、分离、附加、删除 |
| 分区索引 | 自动索引、唯一约束 |
| 性能优化 | 分区裁剪、查询优化 |
📌 下一章预告
下一章将介绍 PostgreSQL 的备份与恢复策略。