一、分区表基础概念

各位同学好,我是老张。今天咱们聊聊分区表。这东西说白了,就是把一张大表拆成多个小表来管理。我刚开始接触分区表时,也觉得不就是分拆嘛,有啥难的?后来在生产环境里栽过跟头,才明白这里面的门道有多深。

先问大家一个问题:一张表里存了 10 亿行数据,你做个全表扫描,得多久?我见过最夸张的一次,某电商的订单表跑了将近 40 分钟。后来改成按月份分区,查询时间直接降到秒级。这就是分区表的价值。

1.1 什么是分区表

分区表,是一种将逻辑上的一张表,物理上拆分成多个独立存储单元的技术。每个存储单元叫一个「分区」。对应用程序来说,它看到的还是一张表,SQL 不用改。但对数据库来说,数据是分散存储的。

举个例子。你有一张订单表 orders,按月份分成 12 个分区。查询 1 月份的数据时,数据库只扫描 1 月那个分区,其他 11 个分区碰都不碰。性能自然就上去了。

核心要点:分区表 = 逻辑统一 + 物理分散。应用层无感知,但底层存储和查询可以并行、裁剪、隔离。

我个人习惯把分区表比作「档案柜」。一张大表就是一个大柜子,里面塞满了文件。找一份文件得翻遍整个柜子。分区表呢,相当于给柜子加了隔层——1 月文件放第一层,2 月放第二层……找文件时直接打开对应那层就行。

1.2 分区键

分区键,就是决定数据「该放哪个分区」的列。它可以是单列,也可以是多列的组合。

选分区键是个技术活。我在项目中遇到过好几次,因为分区键选得不好,导致数据分布严重不均。有的分区塞得满满当当,有的分区几乎空着。这叫什么?这叫「数据倾斜」。分区表最怕这个。

分区键类型 示例 适用场景
时间列 order_date 日志、订单、流水等时序数据
地域列 region_id 按区域划分的业务数据
业务ID user_id 用户数据分片
枚举列 status 状态有限的业务数据

选分区键的经验:优先选查询条件中频繁出现的列。如果查询经常带 WHERE order_date BETWEEN ...,那分区键就选 order_date。这样能最大化「分区裁剪」的效果。

1.3 分区类型

常见的分区类型有四种:范围分区、列表分区、哈希分区、复合分区。每种都有各自的脾气,用错了会很难受。

1.3.1 范围分区(RANGE)

范围分区,是按分区键的值范围来划分。最典型的就是按时间分区。

CREATE TABLE orders (
    order_id    BIGINT,
    order_date  DATE,
    amount      DECIMAL(10,2)
)
PARTITION BY RANGE (order_date) (
    PARTITION p202401 VALUES LESS THAN ('2024-02-01'),
    PARTITION p202402 VALUES LESS THAN ('2024-03-01'),
    PARTITION p202403 VALUES LESS THAN ('2024-04-01'),
    PARTITION p_future VALUES LESS THAN (MAXVALUE)
);

注意最后那个 MAXVALUE 分区。我曾经吃过这个亏——没加它,结果有一条数据日期超出了范围,插入直接报错。生产环境半夜报警,那叫一个酸爽。

范围分区的优点很明显:数据按顺序分布,范围查询效率高。缺点呢?如果数据分布不均匀,比如某个月订单量暴增,那个分区就会成为热点。

1.3.2 列表分区(LIST)

列表分区,是按分区键的值列表来划分。适合枚举值固定的场景。

CREATE TABLE orders_region (
    order_id    BIGINT,
    region      VARCHAR(20),
    amount      DECIMAL(10,2)
)
PARTITION BY LIST (region) (
    PARTITION p_north VALUES IN ('北京', '天津', '河北'),
    PARTITION p_south VALUES IN ('广东', '广西', '海南'),
    PARTITION p_east  VALUES IN ('上海', '江苏', '浙江'),
    PARTITION p_other VALUES IN ('其他')
);

列表分区有个坑:如果插入的数据不在任何列表里,会直接报错。所以最好加一个 DEFAULT 分区兜底。嗯,这里要注意,MySQL 8.0 开始支持 DEFAULT 分区,但 Oracle 的语法略有不同。

1.3.3 哈希分区(HASH)

哈希分区,是对分区键做哈希运算,然后按哈希值模分区数来分配数据。它不关心数据本身的值,只关心哈希结果。

CREATE TABLE orders_hash (
    order_id    BIGINT,
    user_id     BIGINT,
    amount      DECIMAL(10,2)
)
PARTITION BY HASH (user_id)
PARTITIONS 8;

哈希分区的最大好处是数据分布均匀。你想想看,哈希函数天然就是打散的。但缺点也很明显:范围查询基本用不上分区裁剪,因为相邻的值可能被哈希到不同的分区。

我建议哈希分区用在「等值查询」为主的场景。比如按用户 ID 查订单,哈希分区就很合适。

1.3.4 复合分区(COMPOSITE)

复合分区,就是先按一种方式分区,再在每个分区内按另一种方式子分区。说白了就是「分区套分区」。

CREATE TABLE orders_composite (
    order_id    BIGINT,
    order_date  DATE,
    region      VARCHAR(20),
    amount      DECIMAL(10,2)
)
PARTITION BY RANGE (order_date)
SUBPARTITION BY LIST (region)
SUBPARTITION TEMPLATE (
    SUBPARTITION s_north VALUES IN ('北京', '天津', '河北'),
    SUBPARTITION s_south VALUES IN ('广东', '广西', '海南'),
    SUBPARTITION s_east  VALUES IN ('上海', '江苏', '浙江'),
    SUBPARTITION s_other VALUES IN ('其他')
) (
    PARTITION p202401 VALUES LESS THAN ('2024-02-01'),
    PARTITION p202402 VALUES LESS THAN ('2024-03-01'),
    PARTITION p202403 VALUES LESS THAN ('2024-04-01')
);

复合分区的灵活性很高。比如先按月份范围分区,再按地区列表子分区。这样既能做时间范围裁剪,又能做地区过滤。但代价是分区数量会爆炸——3 个月份 × 4 个地区 = 12 个分区。如果分区数太多,管理成本会直线上升。

注意:不是所有数据库都支持复合分区。MySQL 8.0 支持 RANGE + LIST、RANGE + HASH、LIST + HASH 等组合。但 PostgreSQL 的复合分区支持相对有限。做迁移前一定要确认目标库的能力边界。

1.4 四种分区类型对比

分区类型 数据分布方式 分区裁剪效果 数据均匀性 典型场景
RANGE 按值范围 范围查询极好 可能不均匀 时间序列数据
LIST 按值列表 等值查询好 取决于枚举分布 地域、状态分类
HASH 按哈希值 等值查询好 非常均匀 用户ID分片
COMPOSITE 多层组合 多层裁剪 取决于组合方式 复杂业务场景

1.5 分区表知识体系总览

下面这张图,把分区表的核心概念串起来了。我建议你多看几遍,脑子里先搭好框架。

分区表知识体系总览 分区表 分区键 分区类型 分区裁剪 RANGE 范围 LIST 列表 HASH 哈希 COMPOSITE 复合 按值范围划分 适合时间序列 按值列表划分 适合枚举分类 按哈希值分配 数据均匀分布 多层分区组合 灵活但管理复杂 关键注意事项 分区键选择决定性能上限 | 分区数过多会降低管理效率 | 不同数据库语法差异大 迁移前必须验证目标库的分区能力 | 数据倾斜是分区表最大的坑

这张图把分区表的核心脉络理清楚了。从上往下看:分区表依赖分区键来划分数据,分区类型决定了划分的规则,分区裁剪则是性能收益的来源。四种分区类型各有侧重,选型时要结合业务场景。

我的建议:刚开始接触分区表,先从 RANGE 分区入手。它最直观,也最容易理解。等用熟了,再尝试 LIST 和 HASH。复合分区嘛,等你有了一定经验再碰,不然容易把自己绕进去。