2、分区类型详解(上):范围分区、列表分区、哈希分区

分区表的核心,说白了就是「分而治之」。把一张大表拆成若干小表,查询时只扫相关分区,性能自然就上来了。但怎么拆,拆成什么样,这里头门道不少。

我个人习惯把分区类型分成两类:逻辑分区和数据分布分区。范围分区和列表分区属于前者——分区规则跟业务逻辑强相关;哈希分区属于后者——它只管把数据均匀打散,不关心数据本身是什么。

今天我们先聊这三种最基础的分区类型。嗯,先看一张总览图,心里有个谱。

分区类型总览(上) 分区类型 RANGE 范围分区 LIST 列表分区 HASH 哈希分区 按连续区间划分 按离散值列表划分 按哈希函数散列 时间、ID 范围 地区、状态、枚举 均匀分布、无业务倾向

2.1 范围分区(RANGE)

范围分区是最常用、也最直观的一种。你指定一个列作为分区键,然后按值的区间把数据切到不同分区里。

举个例子,订单表按月份分区:

CREATE TABLE orders (
    order_id    BIGINT,
    order_date  DATE,
    customer_id INT,
    amount      DECIMAL(10,2)
)
PARTITION BY RANGE (order_date) (
    PARTITION p_2024_q1 VALUES LESS THAN ('2024-04-01'),
    PARTITION p_2024_q2 VALUES LESS THAN ('2024-07-01'),
    PARTITION p_2024_q3 VALUES LESS THAN ('2024-10-01'),
    PARTITION p_2024_q4 VALUES LESS THAN ('2025-01-01')
);

这里有个细节:VALUES LESS THAN 定义的是上界,不包含边界值。也就是说,2024-03-31 的数据进 p_2024_q1,2024-04-01 的数据进 p_2024_q2。

我的习惯:范围分区一定要留一个「未来分区」。比如上面只规划到 2024 年底,那 2025 年的数据往哪放?要么提前建好 p_2025_q1,要么用 MAXVALUE 兜底。我个人倾向提前建,因为 MAXVALUE 分区一旦数据量大了,拆分起来挺麻烦的。

范围分区的优势很明显:

  • 分区裁剪效果好——查询条件带分区键范围时,优化器直接跳过无关分区。比如 WHERE order_date BETWEEN '2024-05-01' AND '2024-06-30',只扫 p_2024_q2。
  • 方便数据生命周期管理——旧分区可以直接 DROP 或归档,比 DELETE 快几个数量级。
  • 范围查询友好——按时间、ID 范围查数据的场景,天然适配。

但也有坑:

  • 数据倾斜——如果某个时间段数据量暴增(比如双十一),那个分区会特别大,失去分区优势。
  • 分区键选择要谨慎——别选那种频繁更新的列做分区键,否则每次更新都可能跨分区移动数据,性能灾难。
我曾经踩过的坑:有次给日志表做范围分区,按天分区,每天一个分区。结果某天业务方改了日志级别,一天的数据量是平时的 50 倍。那个分区直接撑爆了磁盘 IO,查询全堵在那一个分区上。后来我改成按周分区,并且加了监控,单分区数据量超过阈值就自动告警。

2.2 列表分区(LIST)

列表分区跟范围分区不同,它不是按连续区间,而是按离散值列表来划分。你指定一个列,然后告诉数据库:「这些值放这个分区,那些值放那个分区」。

最适合的场景:地区、状态、类别等枚举值。

CREATE TABLE orders (
    order_id     BIGINT,
    region       VARCHAR(20),
    order_date   DATE,
    amount       DECIMAL(10,2)
)
PARTITION BY LIST (region) (
    PARTITION p_north VALUES IN ('北京', '天津', '河北', '山西'),
    PARTITION p_east  VALUES IN ('上海', '江苏', '浙江', '安徽'),
    PARTITION p_south VALUES IN ('广东', '福建', '海南'),
    PARTITION p_other VALUES IN ('default')
);

注意最后那个 p_other 分区,我用了一个 default 关键字。这相当于兜底——所有没匹配到的值都扔进去。嗯,这个设计很关键。

避坑指南:列表分区一定要考虑「未覆盖值」的问题。我曾经接手过一个系统,列表分区没设 default,结果业务新增了一个地区,插入数据直接报错——ERROR: no partition of relation "orders" found for row。生产环境半夜出这种问题,够你喝一壶的。

列表分区的适用场景:

  • 按地域隔离数据——不同地区的数据可以存在不同分区,方便按地区查询和管理。
  • 按状态分类——比如订单状态:待支付、已支付、已发货、已完成、已取消。每个状态一个分区,查询特定状态时效率很高。
  • 多租户场景——不同租户的数据分到不同分区,租户隔离天然实现。

需要注意的点:

  • 列表值不能重叠——同一个值不能出现在两个分区的 IN 列表里。
  • 分区数量不宜过多——我建议控制在 50 个以内。太多分区会导致元数据膨胀,DDL 操作变慢。
  • default 分区要监控——它很容易变成「垃圾堆」,所有未预期的值都往里塞,最后这个分区可能比所有正常分区都大。

2.3 哈希分区(HASH)

哈希分区跟前面两种完全不同。它不关心数据本身的业务含义,而是对分区键做哈希运算,然后根据哈希值模分区数,决定数据落到哪个分区。

说白了,它的目标只有一个:让数据均匀分布。

CREATE TABLE user_session (
    session_id   VARCHAR(64),
    user_id      BIGINT,
    login_time   TIMESTAMP,
    ip_address   VARCHAR(45)
)
PARTITION BY HASH (user_id)
PARTITIONS 8;

这里 PARTITIONS 8 表示分成 8 个分区。数据库内部对 user_id 做哈希,然后模 8,结果 0-7 分别对应 8 个分区。

哈希分区的核心价值:

  • 数据均匀分布——只要分区键的基数够大,数据基本能均匀散到各个分区。不会出现范围分区那种「一个分区撑死,其他分区饿死」的情况。
  • 适合随机访问——按分区键做等值查询时,直接定位到单个分区,性能很好。
  • 避免热点——写操作可以分散到多个分区,不会集中在某一个分区上。

但哈希分区也有明显的短板:

  • 范围查询不友好——比如 WHERE user_id BETWEEN 1000 AND 2000,这个范围的数据可能散落在所有 8 个分区里,每个分区都得扫一遍。
  • 分区数一旦确定,改起来很麻烦——哈希分区数变了,所有数据都得重新计算哈希、重新分布。虽然数据库可以自动做,但代价很大。
  • 分区键选择要谨慎——如果分区键的基数很小(比如性别,只有男、女),哈希出来也就两种结果,分 8 个分区等于白分。
我的建议:哈希分区最适合那种「没有明显业务分区键,但数据量又大,需要均匀分散」的场景。比如用户行为日志、会话数据、流水号表。我一般在分区数上取 2 的幂次(4、8、16、32),这样哈希取模的效率更高。

2.4 三种分区类型的对比

三种分区各有各的脾气,选哪个得看业务场景。我整理了一张对比表,方便你快速决策:

维度 RANGE 范围分区 LIST 列表分区 HASH 哈希分区
分区依据 连续区间 离散值列表 哈希函数
数据分布 可能倾斜 可能倾斜 均匀分布
范围查询 ✅ 高效 ❌ 不适用 ❌ 低效
等值查询 ✅ 高效(定位到分区) ✅ 高效 ✅ 高效
分区管理 ✅ 方便增删 ✅ 方便增删 ⚠️ 改分区数代价大
典型场景 时间、ID 范围 地区、状态、枚举 均匀散列、无业务倾向
分区键要求 有序、单调递增 离散、有限枚举 基数大、分布均匀

2.5 选型决策思路

你可能会问:那我到底该用哪种?

我的经验是三步走:

  1. 先看有没有天然的业务分区键——比如时间、地区、状态。有的话,优先考虑 RANGE 或 LIST。
  2. 再看查询模式——如果查询经常带范围条件,选 RANGE;如果经常做等值查询且分区键是枚举值,选 LIST;如果查询模式随机,没有明显规律,选 HASH。
  3. 最后看数据增长模式——数据持续增长且需要定期清理旧数据的,RANGE 最合适;数据量稳定但需要均匀分散的,HASH 更靠谱。
重要提醒:分区键一旦选定,后续改起来成本很高。我见过一个项目,上线时用 LIST 分区按省份分,后来业务扩展到全国 300 多个城市,每个城市的数据量差异巨大,LIST 分区完全扛不住。最后花了两个周末做数据迁移,改成了 RANGE + HASH 的组合分区。所以,选分区类型时,想想未来 1-2 年的数据量和查询模式变化。

好了,三种基础分区类型就聊到这儿。范围分区像切蛋糕,按时间或 ID 一刀一刀切;列表分区像分拣快递,按目的地归类;哈希分区像摇骰子,随机但均匀。没有最好的分区类型,只有最适合你业务场景的。