运维笔记

PostgreSQL 分区配置实战:从入门到生产级翻车修复指南

Developer Tools 技术可视化

前言:别被“分区”两个字骗了

先泼盆冷水。分区不是银弹。我见过太多团队,一上来就搞分区,结果查询反而慢了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 非分区

场景非分区表分区表(正确配置)分区表(配置错误)
全表扫描100ms120ms(略慢)150ms
时间范围查询850ms45ms300ms
数据删除(百万级)12s0.8s(truncate分区)5s
插入性能50k/s48k/s30k/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 以内,别贪多。
  • 索引在子分区上单独建,父表索引没用。

最后一句:分区配置好了,真香。配置翻车了,真疼。自己掂量。