上一篇讲 FE 和 BE 时,我们留下了一个问题:
多个 BE 会各自处理一部分数据,可这些数据最初是怎么分过去的?
如果这个问题没想明白,“多个 BE 并行计算”就仍然有一块是空的。
这篇继续用订单表,只讲 StarRocks 数据分布中最重要的两层:分区和分桶。
如果你还没看过上一篇,可以先从这里开始:
👉《一条 SQL 在 StarRocks 里经历了什么?终于看懂 FE 和 BE》
先把结论放在前面:
分区负责先把一张大表按范围分开;分桶负责把每个分区继续拆小,并让这些数据能够分布到多个 BE 上。
还是那张订单表
假设 orders 表不断接收订单数据:
2026 年 6 月订单
2026 年 7 月订单
2026 年 8 月订单
……
现在运营要统计 7 月各城市的销售额:
SELECT
city,
SUM(amount) AS sales
FROM orders
WHERE order_time >= '2026-07-01'
AND order_time < '2026-08-01'
GROUP BY city;
如果所有订单都混在一个无法继续区分的大块里,执行查询时就很难快速判断哪些数据根本不需要读。
StarRocks 的做法不是直接把整张表随意扔到几台 BE 上,而是分两层组织:
第一层:分区
第二层:分桶
图 1:一张表先按月份分区,每个分区再被拆成多个桶,桶中的数据分布到多个 BE。
第一层:分区先决定“查哪一段数据”
分区,就是按照某个业务维度,把一张表划分成几个较大的数据范围。
订单表最常见的做法是按照时间分区。例如按月划分后,可以得到:
6 月分区:保存 6 月订单
7 月分区:保存 7 月订单
8 月分区:保存 8 月订单
当查询条件明确限定为 7 月时,StarRocks 可以判断:
-
6 月分区不需要扫描;
-
7 月分区需要扫描;
-
8 月分区不需要扫描。
这就是分区裁剪。
它的价值很直接:查询不必把整张表全部读一遍,而是先排除与条件无关的分区。
如果订单表保存了三年数据,而本次只查一天或一个月,能不能准确裁掉其他时间范围,通常会明显影响需要扫描的数据量。
分区不等于“一台 BE”
这里要把边界说清楚。
“7 月分区”表示一段数据范围,不表示 7 月的全部数据只放在某一台 BE 上。
一个分区内部还会继续分桶。真正让同一分区的数据进一步拆开并分布到多个 BE 的,是第二层。
第二层:分桶决定“这一段数据怎样继续拆开”
7 月一个月的订单仍然可能很多。
如果整个 7 月分区仍然是一个大块,就很难让多个 BE 同时承担这部分数据的存储和计算。
所以 StarRocks 会把每个分区继续拆成多个桶。
例如,把 7 月分区拆成 4 个桶:
7 月分区
├─ 桶 1:一部分订单
├─ 桶 2:一部分订单
├─ 桶 3:一部分订单
└─ 桶 4:一部分订单
这些桶中的数据再分布到集群中的不同 BE 上。
到了查询阶段,多个 BE 就可以同时读取和处理各自持有的那部分数据,而不是先把整个 7 月分区集中到一台机器再计算。
分桶主要解决两个问题:
-
把一个分区继续拆成更小的数据单元;
-
让数据能够较均衡地分布,从而利用多个 BE 并行处理。
哈希分桶是怎样决定一行数据去哪里的
为了让分桶规则更具体,我们先看最容易理解的哈希分桶。
建表时可以选一列作为分桶键。例如选择 order_id:
DISTRIBUTED BY HASH(order_id)
新订单写入时,StarRocks 会根据 order_id 计算一个哈希结果,再由这个结果决定该行数据进入哪个桶。
可以把过程压缩成:
order_id
↓
计算哈希结果
↓
确定所属的桶
↓
桶中的数据分布在相应 BE 上
相同分桶键值的数据会进入同一个桶。不同键值经过计算后,则会被分散到不同桶中。
选择 order_id 这类取值很多的列,通常更容易让数据分散开。如果选择的列只有少量取值,或者某个值的数据特别多,就可能出现一些桶很大、一些桶很小的情况。
这就是为什么分桶键不能随便选。
不指定分桶键也可以吗
在支持的表类型和版本中,StarRocks 也可以使用随机分桶,不要求指定分桶键,并且可以自动设置分桶数量。
但随机分桶不便于利用某个条件列进行分桶裁剪。为了先看清“数据怎样按规则进入不同桶”,本文仍以哈希分桶为例。
初学阶段也不必一上来就计算应该设多少个桶。先理解分桶键如何影响数据分布,再根据实际数据量和查询方式继续调整。
分区和分桶到底有什么区别
现在把这两层并排放在一起:
分区:先把整张表按时间等范围分成几大段
分桶:再把每个分区拆成多个更小的数据单元
它们回答的是两个不同的问题。
分区回答:这次查询需要哪一段数据
如果查询条件包含分区列,StarRocks 可以通过分区裁剪跳过无关分区。
例如按 order_time 的月份分区,查询 7 月时就可以跳过 6 月和 8 月。
分桶回答:选中的分区怎样交给多个节点处理
7 月分区被选中后,它内部仍然包含多个桶。这些桶中的数据分布在多个 BE 上,可以由多个 BE 并行读取和计算。
如果查询条件恰好包含合适的哈希分桶键,StarRocks 还可能只扫描命中的少量桶,而不是扫描该分区中的全部桶。
所以更准确的记忆方式是:
分区先减少需要看的大范围,分桶再组织这段范围内的数据分布和并行处理。
查询 7 月销售额时,到底少读了什么
现在重新执行开头那条 SQL。
查询条件明确指定了 7 月:
WHERE order_time >= '2026-07-01'
AND order_time < '2026-08-01'
如果表按 order_time 的月份分区,整个过程可以简化为:
-
FE 根据查询条件判断只需要 7 月分区;
-
6 月和 8 月分区被跳过;
-
7 月分区中的多个桶分布在不同 BE 上;
-
相关 BE 并行读取各自的数据,完成过滤和部分聚合;
-
StarRocks 汇总各个 BE 的结果并返回。
图 2:时间条件先裁掉无关月份,只让相关 BE 扫描 7 月分区中的桶。
这次查询没有按 order_id 过滤,所以哈希分桶键不能帮助它只命中某一个桶;7 月分区中的多个桶仍可能都要参与。
但分桶依然有价值:它让 7 月数据被拆开并分布到多个 BE,多个节点可以并行完成这次统计。
这个区别很重要:
-
分区裁剪关注能不能跳过整个分区;
-
分桶裁剪关注能不能跳过分区中的部分桶;
-
即使没有发生分桶裁剪,分桶仍然承担数据分布和并行处理的作用。
建表语句里怎么看出这两层
下面是一份为了讲解而简化的订单表定义:
CREATE TABLE orders (
order_id BIGINT,
order_time DATETIME,
city VARCHAR(50),
amount DECIMAL(18, 2)
)
DUPLICATE KEY(order_id, order_time)
PARTITION BY date_trunc('month', order_time)
DISTRIBUTED BY HASH(order_id);
先不用急着记完整语法,只看最后两行:
PARTITION BY date_trunc('month', order_time)
表示按照 order_time 的月份分区。
DISTRIBUTED BY HASH(order_id)
表示每个分区内部再按照 order_id 进行哈希分桶。
一行订单写入后,会先确定它属于哪个月份分区,再确定它属于该分区中的哪个桶。
也就是:
一行订单
↓
先看 order_time:进入哪个月份分区
↓
再看 order_id:进入该分区中的哪个桶
这就是 StarRocks “分区 + 分桶”两级数据分布最核心的过程。
工作中该怎样选择
这篇不展开完整的表设计方法,但可以先建立三个基础判断。
分区列要贴近常用的数据范围
订单、日志、事件明细通常会按时间查询和清理,因此时间列经常适合作为分区列。
如果大多数查询都带日期范围,按日期或月份分区就更容易通过分区裁剪减少扫描。
分区不能细到失去控制
不是分区越多越好。
数据量较小却按小时甚至按分钟分区,会产生大量很小的分区,增加元数据管理成本。分区粒度应该结合单个周期的数据量、常用查询范围和数据清理周期决定。
分桶键要兼顾均匀和查询条件
哈希分桶键通常希望具备两个特点:
-
取值足够分散,避免数据集中到少数桶;
-
经常作为查询条件时,有机会减少需要扫描的桶。
如果暂时没有把握,不要仅凭字段名字决定。先看真实的数据分布和查询方式,再做选择。
最后,只记住一条路径
读完这一篇,可以先不记各种分区类型和分桶数量。
只记住一行数据写入后的路径:
一行数据
→ 先进入某个分区
→ 再进入该分区中的某个桶
→ 桶中的数据分布到多个 BE
查询时则反过来理解:
先根据条件裁掉无关分区
→ 再扫描命中的桶
→ 多个 BE 并行处理
回到开头的问题:数据为什么会分布到不同 BE?
因为 StarRocks 不是直接把整张表当成一个整体存放,而是先分区、再分桶,再把桶中的数据分布到集群节点上。
下一篇,我们继续解决建表时一定会遇到的问题:
明细模型、主键模型、聚合模型和更新模型到底有什么区别,一张业务表该选哪一种?