分区类型详解:水平分区(Range、List、Hash)、垂直分区、混合分区策略对比

分区策略选对了,数据库性能能翻倍。选错了,维护成本直线上升。我见过不少团队,一上来就搞 Hash 分区,结果查询慢得离谱——说白了,分区不是万能药,得对症下药。

这一节,咱们把几种主流分区策略掰开揉碎讲清楚。我会结合自己踩过的坑,帮你避开那些常见的雷区。

水平分区:把一张大表“横着切”

水平分区的核心思路很简单:把一张表的数据行,按某种规则分散到多个物理分区中。每个分区的表结构完全一样,只是数据不同。

打个比方,你有一本 1000 页的电话簿。水平分区就是把它拆成 10 本小册子,每本 100 页。查某个人的电话时,你只需要翻对应的小册子就行。

Range 分区:按范围切分

Range 分区是我用得最多的策略。它根据某个列的取值范围,把数据分配到不同分区。

适用场景:时间序列数据、按 ID 范围查询、日志数据归档。

CREATE TABLE orders (
    order_id INT NOT NULL,
    customer_id INT,
    order_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p_2021 VALUES LESS THAN (2022),
    PARTITION p_2022 VALUES LESS THAN (2023),
    PARTITION p_2023 VALUES LESS THAN (2024),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

我个人习惯用 Range 分区处理日志表。为什么?因为历史数据可以定期归档,甚至直接把旧分区干掉,比 DELETE 快得多。

小技巧:Range 分区一定要留一个 MAXVALUE 分区。我曾经吃过亏——业务突然新增了未来数据,结果插入失败,线上告警响了一整夜。

List 分区:按枚举值切分

List 分区是按离散值列表来划分的。说白了,就是“属于这个列表的,进这个分区”。

CREATE TABLE user_profiles (
    user_id INT,
    region VARCHAR(20),
    nickname VARCHAR(50)
)
PARTITION BY LIST COLUMNS(region) (
    PARTITION p_north VALUES IN ('北京', '天津', '河北'),
    PARTITION p_south VALUES IN ('广东', '广西', '海南'),
    PARTITION p_east VALUES IN ('上海', '江苏', '浙江'),
    PARTITION p_other VALUES IN ('其他')
);

我记得有一次做多租户系统,每个租户的数据量差异很大。用 List 分区按租户 ID 切分,查询时直接走对应分区,效果立竿见影。

注意:List 分区不支持范围匹配。如果你要查“华北地区所有用户”,但分区是按省份定义的,那就得扫描多个分区。设计时一定要想清楚查询模式。

Hash 分区:按哈希值散列

Hash 分区通过哈希函数计算分区键的值,然后均匀分布到指定数量的分区中。它不关心数据的实际含义,只关心“能不能均匀散开”。

CREATE TABLE sessions (
    session_id VARCHAR(64),
    user_id INT,
    login_time DATETIME
)
PARTITION BY HASH (user_id)
PARTITIONS 8;

Hash 分区最大的优点是数据分布均匀。但缺点也很明显——范围查询基本废了。你想想看,查某个时间段内的会话,Hash 分区得扫所有分区,还不如不分区。

我的建议:Hash 分区适合“点查询”场景,比如按用户 ID 查详情。如果是报表类需求,别用 Hash,Range 分区更合适。

垂直分区:把一张大表“竖着切”

垂直分区不是按行切,而是按列切。把一张宽表拆成多张窄表,每张表的主键相同。

举个例子,用户表有 50 个字段。经常查询的只有用户名、邮箱、手机号这 5 个字段。剩下的 45 个字段(比如地址、偏好设置)很少用到。这时候就可以垂直拆分:

-- 主表:高频字段
CREATE TABLE users_core (
    user_id INT PRIMARY KEY,
    username VARCHAR(50),
    email VARCHAR(100),
    phone VARCHAR(20)
);

-- 扩展表:低频字段
CREATE TABLE users_extended (
    user_id INT PRIMARY KEY,
    address TEXT,
    preferences JSON,
    created_at DATETIME
);

我遇到过一个大坑:某张表有 80 多个字段,每次查询都 SELECT *,结果 IO 开销巨大。垂直拆分后,高频查询只扫 5 个字段,性能提升了 3 倍。

注意:垂直分区会增加 JOIN 操作。如果拆分得太细,查询反而变慢。我的经验是:把访问频率差异大的列分开,频率相近的列尽量放在一起。

混合分区:组合拳才是王道

实际项目中,单一分区策略往往不够用。混合分区(也叫组合分区)就是把两种策略结合起来。

最常见的组合是 Range + Hash:先按时间 Range 分区,再在每个时间分区内按用户 ID Hash 子分区。

CREATE TABLE orders (
    order_id INT,
    user_id INT,
    order_date DATE,
    amount DECIMAL(10,2)
)
PARTITION BY RANGE (YEAR(order_date))
SUBPARTITION BY HASH (user_id)
SUBPARTITIONS 4 (
    PARTITION p_2022 VALUES LESS THAN (2023),
    PARTITION p_2023 VALUES LESS THAN (2024),
    PARTITION p_2024 VALUES LESS THAN (2025)
);

这样做的好处是:既能按时间快速裁剪数据,又能让每个时间分区内的数据均匀分布。我做过一个电商订单系统,日活千万级,就是用这种策略扛住的。

四种策略对比

策略 切分方式 适用场景 优点 缺点
Range 分区 按值范围 时间序列、日志、归档 范围查询快、易于管理 数据可能倾斜
List 分区 按枚举值 地域、分类、租户 语义清晰、精准定位 扩展性差、不支持范围
Hash 分区 按哈希值 点查询、均匀分布 数据均匀、避免热点 范围查询低效
垂直分区 按列拆分 宽表、冷热数据分离 减少 IO、提升缓存命中 增加 JOIN、维护复杂

如何选择?一张流程图帮你理清思路

下面这张图是我自己总结的决策流程。每次做分区设计时,我都会过一遍这个逻辑。

分区策略决策流程图 分区策略决策流程 开始分区设计 表是否很宽(列数 > 30)? 是 垂直分区 否 查询以范围还是点查为主? 范围查询 是否有自然范围(时间/ID)? 是 Range 分区 否 是否有明确的枚举值? 是 List 分区 否 Hash 分区 提示:实际项目中常组合使用,如 Range + Hash 混合分区 先按时间 Range 分区,再按用户 ID Hash 子分区

避坑指南

最后,分享几个我亲身踩过的坑:

  • 分区键选择不当:我曾经用 UUID 做 Hash 分区键,结果数据分布极不均匀。后来改用自增 ID,问题解决。
  • 分区数量过多:MySQL 单个表最多支持 8192 个分区。但别真搞那么多——分区太多,元数据管理开销很大。我一般控制在 64 个以内。
  • 忽略 NULL 值:Range 分区中,NULL 值会进入最小分区。如果你没考虑到,可能导致小分区数据量暴增。
  • 垂直分区后忘记同步:有一次我拆完表,忘了在应用层做事务补偿,结果核心表和扩展表数据不一致。后来加了分布式事务才搞定。

核心原则:分区策略没有银弹。先分析查询模式,再选策略。如果拿不准,就从 Range 分区开始——它最直观,也最容易调优。