2. 分区表基础概念
在讨论分区表设计之前,必须先厘清四个基础概念:分区定义、分区键、分区粒度,以及分区表与普通表的核心差异。这四个概念决定了后续所有性能优化与回滚策略的边界。很多团队在分区表上踩坑,并不是因为不会写 DDL,而是因为一开始对“分区到底是什么”理解偏差,导致分区键选错、粒度失控,最终既没有提升查询性能,也无法安全回滚。
一、分区定义:逻辑一张表,物理多段存储
分区表(Partitioned Table)的本质是:对外仍然是一张逻辑表,对内被拆分成多个物理子表(分区)。应用程序使用同一张表名、同一套 SQL 访问,数据库优化器根据分区键自动完成“分区裁剪(Partition Pruning)”,只扫描命中的分区,而不是全表。
以 PostgreSQL 声明式分区为例:
CREATE TABLE orders (
order_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL,
amount NUMERIC(12,2),
status SMALLINT
) PARTITION BY RANGE (created_at);
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');
这里 orders 是逻辑父表,orders_2024_01、orders_2024_02 是物理分区。查询 WHERE created_at >= '2024-02-05' 时,优化器只会访问 orders_2024_02。这就是分区表最核心的价值:把“全表扫描”降级为“分区扫描”。
二、分区键:决定数据归属与裁剪能力
分区键(Partition Key)是分区表中用于决定“一行数据进入哪个分区”的列或表达式。它必须满足两个条件:出现在绝大多数查询的过滤条件中,且取值分布相对均匀、可预测。
常见分区键类型包括:
- 时间列:如
created_at、order_date,适合日志、订单、流水类数据,天然支持按时间归档与回滚。 - 业务维度列:如
tenant_id、region_code,适合多租户 SaaS,便于按租户隔离与迁移。 - 哈希列:如
user_id做 HASH 分区,用于打散热点写入。 - 组合键:如
(tenant_id, created_at),兼顾租户隔离与时间裁剪。
三、分区粒度:越细不一定越好
分区粒度指每个分区承载的数据时间跨度或数据量。粒度太粗,单分区过大,裁剪收益低;粒度太细,分区数量爆炸,元数据膨胀,DDL 与查询规划开销上升。
经验上,单分区数据量控制在 1000 万行以内或 10GB 以内较为稳妥。以订单表为例:
| 粒度 | 分区数量(3年) | 优点 | 风险 |
|---|---|---|---|
| 按年 | 3 | 管理简单 | 单分区过大,裁剪收益低 |
| 按月 | 36 | 平衡性好,归档方便 | 需定期预建分区 |
| 按天 | 1095 | 裁剪精准,回滚快 | 分区数量多,元数据压力大 |
| 按小时 | 26280 | 适合超高频写入 | 规划开销显著,慎用 |
多数业务系统推荐按月分区,高频写入场景可退化为按天分区。粒度选择应同时考虑“查询裁剪收益”和“回滚操作成本”。
四、分区表与普通表的核心差异
分区表不是普通表的简单升级,二者在存储、索引、约束、DDL 行为上都有本质区别。理解这些差异,才能避免“以为分区了就能提速”的误区。
从上图可以清晰看到:普通表的“删除历史数据”是 DELETE,而分区表是 DROP PARTITION。这正是分区表在回滚与归档场景下最大的优势——删除一个分区是元数据操作,几乎瞬间完成,且不会产生大量回滚日志。
五、常见误区与复盘
status(只有几个取值)做分区键,会导致数据严重倾斜,某些分区巨大、某些分区空置,裁剪收益极低。误区二:分区粒度按天但从不预建分区。新数据写入时找不到对应分区,直接报错或落入默认分区,导致默认分区膨胀。
误区三:以为分区表自动加速所有查询。如果查询条件不带分区键,优化器无法裁剪,性能与普通表无异,甚至因分区数量多而规划更慢。
误区四:在分区表上建全局唯一索引。多数数据库要求唯一索引必须包含分区键,否则无法创建,设计时需提前规划。
复盘一个真实场景:某订单表按 created_at 按月分区,但查询接口大量使用 order_id 精确查询。由于 order_id 不是分区键,每次查询都要扫描所有分区。后来团队将分区键改为 (created_at, order_id) 组合,并在应用层强制携带时间范围,查询耗时从 800ms 降到 30ms。这说明:分区键必须匹配真实查询模式,而不是“看起来合理”的列。
总结来说,分区定义解决“是什么”,分区键解决“怎么分”,分区粒度解决“分多细”,而分区表与普通表的差异决定了“能带来什么收益、要付出什么代价”。把这四点想清楚,后续的性能优化与回滚设计才有稳固的地基。