前言:别被“分区”两个字骗了
先泼盆冷水。分区不是银弹。我见过太多团队,一上来就搞分区,结果查询反而慢了3倍,运维复杂度直接爆炸。我自己就在生产环境里踩过这个坑——某次凌晨3点,分区表默认分区炸了,监控直接拉警报。
PostgreSQL 从 10 开始支持声明式分区,这玩意确实香。但配置不对,分分钟翻车。今天这篇,不讲废话,直接上硬核配置和踩坑实录。
分区类型:选错类型,后面全白干
范围分区(Range Partitioning)
最常用,没有之一。按时间、ID范围切分。
CREATE TABLE orders (
id BIGSERIAL,
created_at DATE NOT NULL,
amount DECIMAL(10,2)
) PARTITION BY RANGE (created_at);
列表分区(List Partitioning)
按固定值列表分,比如按地区、状态。
CREATE TABLE orders (
id BIGSERIAL,
region TEXT NOT NULL,
amount DECIMAL(10,2)
) PARTITION BY LIST (region);
哈希分区(Hash Partitioning)
这玩意我一般不用。除非你确实有均匀分布的需求,否则别碰。数据倾斜会让你怀疑人生。
实战配置:手把手教你搭一个分区表
第一步:创建父表
CREATE TABLE events (
id BIGSERIAL,
event_time TIMESTAMPTZ NOT NULL,
event_type TEXT,
payload JSONB
) PARTITION BY RANGE (event_time);
第二步:创建子分区
CREATE TABLE events_2024_q1 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
CREATE TABLE events_2024_q2 PARTITION OF events
FOR VALUES FROM ('2024-04-01') TO ('2024-07-01');
CREATE TABLE events_2024_q3 PARTITION OF events
FOR VALUES FROM ('2024-07-01') TO ('2024-10-01');
第三步:创建默认分区(血的教训)
这里必须加。不加默认分区,插入数据不在定义范围内,直接报错。
CREATE TABLE events_default PARTITION OF events DEFAULT;
但是——默认分区是个坑。它会把所有不匹配的数据都吞进去。如果你不监控它的大小,它会变成一个巨大的“垃圾桶”,查询性能直接崩。
第四步:索引怎么建?
子分区上,索引必须单独建。父表上的索引不会自动传播到子分区。
CREATE INDEX idx_events_2024_q1_time ON events_2024_q1 (event_time);
CREATE INDEX idx_events_2024_q2_time ON events_2024_q2 (event_time);
自动化分区管理:pg_partman 真香
手动创建分区?别傻了。用 pg_partman 吧。
-- 安装扩展
CREATE EXTENSION pg_partman;
-- 配置自动分区
SELECT partman.create_parent(
p_parent_table := 'public.events',
p_control := 'event_time',
p_type := 'native',
p_interval := '1 month',
p_premake := 3
);
这个 p_premake := 3 表示提前创建3个分区。别设太大,否则分区表元数据膨胀,查询优化器会变慢。
性能对比:分区 vs 非分区
| 场景 | 非分区表 | 分区表(正确配置) | 分区表(配置错误) |
|---|---|---|---|
| 全表扫描 | 100ms | 120ms(略慢) | 150ms |
| 时间范围查询 | 850ms | 45ms | 300ms |
| 数据删除(百万级) | 12s | 0.8s(truncate分区) | 5s |
| 插入性能 | 50k/s | 48k/s | 30k/s |
| 维护复杂度 | 低 | 中 | 高 |
关键结论:分区在范围查询和批量删除上有绝对优势,但插入性能会略微下降。如果插入是瓶颈,别分区。
常见翻车现场
1. 分区键选择错误
选了 UPDATE 频繁的列做分区键。PostgreSQL 不支持跨分区更新分区键,会报错。老老实实用 INSERT + DELETE 替代。
2. 分区数量爆炸
超过 1000 个分区,查询计划时间会暴涨。我们团队实测,2000个分区时,EXPLAIN 耗时从 2ms 涨到 200ms。
3. 默认分区失控
上面说了,默认分区会吞掉所有不匹配的数据。你以为是“兜底”,实际是“定时炸弹”。
怎么监控?
SELECT schemaname, tablename, n_live_tup
FROM pg_stat_user_tables
WHERE tablename LIKE '%default%';
FAQ
Q: 分区表支持外键吗? A: 支持。但外键只能引用分区表本身,不能引用其他分区。而且性能上会有折损。
Q: 可以动态添加分区吗?
A: 可以。CREATE TABLE new_part PARTITION OF parent FOR VALUES FROM (...) TO (...); 不需要锁表。
Q: 分区表如何备份?
A: 建议用 pg_dump 整个数据库备份。单独备份分区容易出问题,因为元数据在父表上。
Q: 分区表能用在 OLTP 场景吗? A: 可以,但要谨慎。OLTP 场景下,分区带来的额外开销(计划、元数据)可能抵消收益。建议先压测。
Q: 分区键能改吗? A: 不能。必须重建整个分区表。所以选分区键时,想清楚。
总结建议
- 分区不是万能的。先确认你的查询模式是否适合。
- 默认分区一定要加,但一定要监控。
- 用
pg_partman自动化管理,别手写。 - 分区数量控制在 100 以内,别贪多。
- 索引在子分区上单独建,父表索引没用。
最后一句:分区配置好了,真香。配置翻车了,真疼。自己掂量。