3、范围分区:原理、创建与实战

范围分区,说白了就是按数据值的区间来分堆。比如按日期、按ID范围、按金额区间。这是最直观、最常用的分区方式,没有之一。

我个人习惯,只要业务上有明确的数值或日期范围,优先考虑范围分区。为什么?因为它天然符合查询的“范围扫描”场景——你查某个月的数据,它只扫那个月的分区,其他分区碰都不碰。

3.1 范围分区原理

范围分区的核心逻辑就一句话:根据分区键的值,判断它落在哪个区间,就放到哪个分区。

每个分区都有一个上限(LESS THAN),数据进来时,数据库拿分区键的值跟这些上限比。找到第一个大于等于它的上限,就归到那个分区。

关键点:分区是按顺序定义的。最后一个分区如果没有上限,就是“兜底分区”,所有超出前面范围的数据都会掉进去。

我记得有一次,团队里有人建分区表时忘了加最后一个兜底分区。结果数据一跑,超出范围的记录直接报错,整个采集任务挂了。嗯,从那以后我每次建表都会检查:有没有留MAXVALUE?

范围分区的底层实现,其实就是在每个分区上建了一个独立的存储段。每个段有自己的数据文件、索引段。查询时,优化器通过分区裁剪(Partition Pruning)直接跳过无关分区。

为什么会这样?因为每个分区的元数据里记录了它的值范围。优化器一看查询条件,就知道哪些分区不满足,直接剪掉。这就是范围分区性能好的根本原因。

3.2 创建范围分区表

创建语法不复杂,但有几个坑。我先给个标准模板,再拆开讲。

CREATE TABLE采集日志 (
    log_id      NUMBER,
    device_id   VARCHAR2(20),
    log_time    DATE,
    log_level   VARCHAR2(10),
    message     VARCHAR2(4000)
)
PARTITION BY RANGE (log_time) (
    PARTITION p_2024_q1 VALUES LESS THAN (DATE '2024-04-01'),
    PARTITION p_2024_q2 VALUES LESS THAN (DATE '2024-07-01'),
    PARTITION p_2024_q3 VALUES LESS THAN (DATE '2024-10-01'),
    PARTITION p_2024_q4 VALUES LESS THAN (DATE '2025-01-01'),
    PARTITION p_future   VALUES LESS THAN (MAXVALUE)
);

这里要注意几个点:

  • 分区键必须是主键或唯一键的子集——这是硬约束。如果主键是(log_id, device_id),那分区键可以是log_id,但不能是log_time。我踩过这个坑,建表时没注意,报错后改了半天。
  • 分区值必须连续且不重叠——每个分区的上限就是下一个分区的下限。你写VALUES LESS THAN (100),下一个就得从100开始。
  • MAXVALUE只能有一个——而且必须放在最后。它代表“所有其他值”。

我的习惯:不管业务上有没有超出范围的可能,我都会加一个MAXVALUE分区。哪怕它永远用不上,也比报错强。这叫“防御性建表”。

3.3 范围分区实战案例

说个真实场景。我之前做工业物联网数据采集,每天从几千台设备收数据,单日数据量在500万条左右。原始表叫sensor_data,按时间戳分区。

一开始没分区,查询某台设备一周的数据要扫全表,耗时40秒。后来改成按月范围分区,同样的查询降到0.8秒。你想想看,50倍的提升。

下面是我当时用的建表语句:

CREATE TABLE sensor_data (
    device_id   VARCHAR2(32),
    ts          TIMESTAMP,
    sensor_type VARCHAR2(10),
    value       NUMBER(10,2),
    quality     NUMBER(1)
)
PARTITION BY RANGE (ts) (
    PARTITION p_202401 VALUES LESS THAN (TIMESTAMP '2024-02-01 00:00:00'),
    PARTITION p_202402 VALUES LESS THAN (TIMESTAMP '2024-03-01 00:00:00'),
    PARTITION p_202403 VALUES LESS THAN (TIMESTAMP '2024-04-01 00:00:00'),
    PARTITION p_202404 VALUES LESS THAN (TIMESTAMP '2024-05-01 00:00:00'),
    PARTITION p_202405 VALUES LESS THAN (TIMESTAMP '2024-06-01 00:00:00'),
    PARTITION p_202406 VALUES LESS THAN (TIMESTAMP '2024-07-01 00:00:00'),
    PARTITION p_202407 VALUES LESS THAN (TIMESTAMP '2024-08-01 00:00:00'),
    PARTITION p_202408 VALUES LESS THAN (TIMESTAMP '2024-09-01 00:00:00'),
    PARTITION p_202409 VALUES LESS THAN (TIMESTAMP '2024-10-01 00:00:00'),
    PARTITION p_202410 VALUES LESS THAN (TIMESTAMP '2024-11-01 00:00:00'),
    PARTITION p_202411 VALUES LESS THAN (TIMESTAMP '2024-12-01 00:00:00'),
    PARTITION p_202412 VALUES LESS THAN (TIMESTAMP '2025-01-01 00:00:00'),
    PARTITION p_overflow VALUES LESS THAN (MAXVALUE)
);

这里我按月分区,每个月一个分区。为什么不是按天?因为按天分区数太多(一年365个),管理起来麻烦。按月刚好,一年12个分区,查询一个月的数据也只扫一个分区。

我曾经试过按周分区,结果发现跨周查询反而变慢了——因为要合并多个分区。所以分区粒度不是越细越好,要跟查询模式匹配。

下面这张图能帮你理解范围分区的整体流程:

范围分区核心流程 采集数据流入 分区键值判断(ts) 分区1:ts < 2024-02-01 分区2:ts < 2024-03-01 分区N:ts < MAXVALUE 独立存储段1 独立存储段2 独立存储段N 查询时只扫描匹配分区,其他分区自动跳过(分区裁剪)

再补充一个常见场景:数据归档。我们当时要求保留最近12个月的数据,历史数据要归档到冷存储。有了范围分区,这事就简单了:

-- 删除最旧的分区(直接释放空间)
ALTER TABLE sensor_data DROP PARTITION p_202401;

-- 或者把旧分区移到归档表空间
ALTER TABLE sensor_data MOVE PARTITION p_202401 TABLESPACE archive_ts;

你看,一条命令就搞定。不用逐条删数据,不用重建索引。这就是分区带来的运维红利。

注意:DROP PARTITION会直接删除分区内的所有数据,不可恢复。操作前一定要确认分区范围是否正确。我曾经手滑删错了分区,还好有备份,不然就凉了。

最后,我整理了一个范围分区与其他分区方式的对比,方便你选型时参考:

特性 范围分区 列表分区 哈希分区
分区依据 连续值区间 离散值列表 哈希函数
典型场景 时间、ID范围 地区、状态码 均匀分布
分区裁剪效果 极好(范围查询) 好(等值查询) 一般(需全分区扫描)
分区管理 易(增删分区) 易(增删值) 难(需重新哈希)
数据倾斜风险 中(需合理设区间) 高(热门值集中) 低(哈希均匀)

总结一下我的经验:范围分区是数据采集系统里最常用的分区方式,没有之一。它简单、直观、性能好,特别适合按时间分区的场景。但要注意分区粒度的选择、兜底分区的设置,以及分区键与主键的关系。把这些坑避开了,范围分区就是你手里最趁手的工具。