分区类型详解:水平分区(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、维护复杂 |
如何选择?一张流程图帮你理清思路
下面这张图是我自己总结的决策流程。每次做分区设计时,我都会过一遍这个逻辑。
避坑指南
最后,分享几个我亲身踩过的坑:
- 分区键选择不当:我曾经用 UUID 做 Hash 分区键,结果数据分布极不均匀。后来改用自增 ID,问题解决。
- 分区数量过多:MySQL 单个表最多支持 8192 个分区。但别真搞那么多——分区太多,元数据管理开销很大。我一般控制在 64 个以内。
- 忽略 NULL 值:Range 分区中,NULL 值会进入最小分区。如果你没考虑到,可能导致小分区数据量暴增。
- 垂直分区后忘记同步:有一次我拆完表,忘了在应用层做事务补偿,结果核心表和扩展表数据不一致。后来加了分布式事务才搞定。
核心原则:分区策略没有银弹。先分析查询模式,再选策略。如果拿不准,就从 Range 分区开始——它最直观,也最容易调优。