3、分区类型详解(下):复合分区、间隔分区与引用分区
上一节我们把 RANGE、LIST、HASH 三种基础分区讲透了。这一节要聊的,是真正能解决复杂业务场景的“进阶武器”。
说实话,我在早期做分区设计时,总觉得单层分区就够了。直到有一次遇到一个订单表——既要按时间归档,又要按地域快速过滤,单层 RANGE 根本扛不住。嗯,从那以后,复合分区就成了我的标配。
3.1 复合分区:RANGE-HASH 与 RANGE-LIST
复合分区,说白了就是“分区里套子分区”。主分区决定数据的大致流向,子分区做更细的物理隔离。Oracle 里叫“复合分区”,MySQL 8.0 也支持了类似语法。
核心价值:主分区解决“裁剪”,子分区解决“并发”或“局部管理”。
3.1.1 RANGE-HASH 复合分区
这种组合我用的最多。主分区按 RANGE 做时间滚动,子分区按 HASH 把数据打散到多个物理段。既保留了 RANGE 的快速清理能力,又解决了单分区数据量过大的问题。
CREATE TABLE orders (
order_id NUMBER,
customer_id NUMBER,
order_date DATE,
amount NUMBER
)
PARTITION BY RANGE (order_date)
SUBPARTITION BY HASH (customer_id) SUBPARTITIONS 4
(
PARTITION p_2023 VALUES LESS THAN (TO_DATE('2024-01-01','YYYY-MM-DD')),
PARTITION p_2024 VALUES LESS THAN (TO_DATE('2025-01-01','YYYY-MM-DD')),
PARTITION p_future VALUES LESS THAN (MAXVALUE)
);
你看,每个 RANGE 分区下自动创建 4 个 HASH 子分区。数据写入时,先按 order_date 落到对应年份分区,再按 customer_id 的哈希值均匀分布到 4 个子分区。
我的习惯:子分区数量建议设为 2 的幂次(2、4、8、16)。HASH 算法对 2 的幂次分布最均匀。我曾经踩过坑,设了 6 个子分区,结果数据倾斜严重,查了半天才发现是分区数的问题。
3.1.2 RANGE-LIST 复合分区
RANGE-LIST 适合那种“主维度按时间,子维度按枚举值”的场景。比如按省份、按业务类型、按状态码。
CREATE TABLE sales (
sale_id NUMBER,
sale_date DATE,
region VARCHAR2(20),
product_id NUMBER
)
PARTITION BY RANGE (sale_date)
SUBPARTITION BY LIST (region)
(
PARTITION p_2023 VALUES LESS THAN (TO_DATE('2024-01-01','YYYY-MM-DD'))
(
SUBPARTITION p_2023_north VALUES ('北京','天津','河北'),
SUBPARTITION p_2023_south VALUES ('广东','深圳','海南'),
SUBPARTITION p_2023_other VALUES (DEFAULT)
),
PARTITION p_2024 VALUES LESS THAN (TO_DATE('2025-01-01','YYYY-MM-DD'))
(
SUBPARTITION p_2024_north VALUES ('北京','天津','河北'),
SUBPARTITION p_2024_south VALUES ('广东','深圳','海南'),
SUBPARTITION p_2024_other VALUES (DEFAULT)
)
);
这里每个年份分区下,都按地域做了 LIST 子分区。查询时如果带上 sale_date 和 region,分区裁剪会直接定位到最细粒度的子分区。
注意:RANGE-LIST 的子分区定义必须显式写出,不能像 HASH 那样自动生成。如果 LIST 值列表很长,DDL 会变得很臃肿。我建议只对取值稳定的列使用 LIST 子分区。
3.2 间隔分区(INTERVAL)
间隔分区是我个人最推崇的“懒人神器”。传统 RANGE 分区需要你手动加分区,一旦忘记加,新数据就报错。间隔分区可以自动创建新分区。
CREATE TABLE audit_log (
log_id NUMBER,
log_time TIMESTAMP,
message VARCHAR2(4000)
)
PARTITION BY RANGE (log_time)
INTERVAL (NUMTODSINTERVAL(1, 'DAY'))
(
PARTITION p_initial VALUES LESS THAN (TO_DATE('2024-01-01','YYYY-MM-DD'))
);
这条语句执行后,你只需要定义一个初始分区。当 log_time 超过 2024-01-01 时,Oracle 会自动创建下一个分区,分区名是系统生成的 SYS_Pnnn 格式。
避坑指南:我曾经在一个日志表上用了间隔分区,跑了大半年都没问题。直到有一天 DBA 要清理旧数据,发现分区名全是 SYS_P 开头,根本看不出对应哪一天。从那以后,我养成了一个习惯——定期用 ALTER TABLE RENAME PARTITION 把系统分区名改成有业务含义的名字。
间隔分区支持多种间隔单位:
| 间隔类型 | 语法示例 | 适用场景 |
|---|---|---|
| 天 | INTERVAL (1 DAY) |
高频日志、流水表 |
| 月 | INTERVAL (1 MONTH) |
月度报表、归档表 |
| 年 | INTERVAL (1 YEAR) |
年度数据、历史库 |
| 数字 | INTERVAL (100) |
数值型主键范围分区 |
重要限制:间隔分区只能用于单列 RANGE 分区,不能用于复合分区的主分区。另外,如果初始分区使用了 MAXVALUE,间隔分区会失效。这两个坑我都在生产环境遇到过,切记。
3.3 引用分区(Reference Partitioning)
引用分区是 Oracle 11g 引入的特性,解决了一个很实际的问题:父子表如何保持分区对齐?
举个例子,订单表 orders 按 order_date 做了 RANGE 分区。订单明细表 order_items 没有 order_date 列,只有 order_id。如果明细表也按时间分区,就得冗余存储日期字段。引用分区可以让你“继承”父表的分区结构。
-- 父表:按 order_date 分区
CREATE TABLE orders (
order_id NUMBER PRIMARY KEY,
order_date DATE,
customer_id NUMBER
)
PARTITION BY RANGE (order_date)
(
PARTITION p_2023 VALUES LESS THAN (TO_DATE('2024-01-01','YYYY-MM-DD')),
PARTITION p_2024 VALUES LESS THAN (TO_DATE('2025-01-01','YYYY-MM-DD'))
);
-- 子表:引用父表分区
CREATE TABLE order_items (
order_id NUMBER NOT NULL,
item_id NUMBER,
product_id NUMBER,
quantity NUMBER,
CONSTRAINT fk_order FOREIGN KEY (order_id) REFERENCES orders(order_id)
)
PARTITION BY REFERENCE (fk_order);
你看,order_items 没有定义任何分区键,只是通过外键约束引用了父表。Oracle 会自动把子表的数据放到与父表相同的分区里。父表 p_2023 分区里的订单,其明细一定在子表的 p_2023 分区里。
性能红利:引用分区最大的好处是——做分区裁剪时,子表不需要额外的过滤条件。你查 order_date 在 2023 年的订单,Oracle 自动只扫描父表和子表的 p_2023 分区。这在做 JOIN 查询时效果非常明显。
我的建议:引用分区适合“主从表”结构,比如订单-明细、客户-地址、单据-分录。但要注意,父表的分区键必须在主键或唯一键中。如果父表分区键不是主键的一部分,引用分区用不了。
3.4 三种进阶分区的选型对比
为了让你快速决策,我把这三种分区放在一起对比:
| 特性 | 复合分区 | 间隔分区 | 引用分区 |
|---|---|---|---|
| 自动创建分区 | 否 | 是 | 否(继承父表) |
| 分区键数量 | 2 个维度 | 1 个维度 | 0 个(继承) |
| 适用场景 | 时间+地域/时间+ID | 时间序列、日志 | 父子表对齐 |
| 管理复杂度 | 中 | 低 | 中 |
| 分区裁剪效率 | 高(两级裁剪) | 高 | 高(JOIN 时) |
| 数据库支持 | Oracle、MySQL 8.0 | Oracle、PostgreSQL | Oracle 11g+ |
3.5 知识体系总览
下面这张图把三种进阶分区的关系和适用路径梳理清楚了:
这三种进阶分区,没有绝对的优劣。复合分区适合“精细化管理”,间隔分区适合“自动化运维”,引用分区适合“关联查询优化”。你根据业务场景选就行。