数据为什么会分到不同 BE?StarRocks 的分区和分桶终于讲清了

0 阅读9分钟

上一篇讲 FE 和 BE 时,我们留下了一个问题:

多个 BE 会各自处理一部分数据,可这些数据最初是怎么分过去的?

如果这个问题没想明白,“多个 BE 并行计算”就仍然有一块是空的。

这篇继续用订单表,只讲 StarRocks 数据分布中最重要的两层:分区和分桶。

如果你还没看过上一篇,可以先从这里开始:

👉《一条 SQL 在 StarRocks 里经历了什么?终于看懂 FE 和 BE》

先把结论放在前面:

分区负责先把一张大表按范围分开;分桶负责把每个分区继续拆小,并让这些数据能够分布到多个 BE 上。

还是那张订单表

假设 orders 表不断接收订单数据:

20266 月订单
20267 月订单
20268 月订单
……

现在运营要统计 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 的月份分区,整个过程可以简化为:

  1. FE 根据查询条件判断只需要 7 月分区;

  2. 6 月和 8 月分区被跳过;

  3. 7 月分区中的多个桶分布在不同 BE 上;

  4. 相关 BE 并行读取各自的数据,完成过滤和部分聚合;

  5. StarRocks 汇总各个 BE 的结果并返回。

图片

图 2:时间条件先裁掉无关月份,只让相关 BE 扫描 7 月分区中的桶。

这次查询没有按 order_id 过滤,所以哈希分桶键不能帮助它只命中某一个桶;7 月分区中的多个桶仍可能都要参与。

但分桶依然有价值:它让 7 月数据被拆开并分布到多个 BE,多个节点可以并行完成这次统计。

这个区别很重要:

  • 分区裁剪关注能不能跳过整个分区;

  • 分桶裁剪关注能不能跳过分区中的部分桶;

  • 即使没有发生分桶裁剪,分桶仍然承担数据分布和并行处理的作用。

建表语句里怎么看出这两层

下面是一份为了讲解而简化的订单表定义:

CREATE TABLE orders (
    order_id   BIGINT,
    order_time DATETIME,
    city       VARCHAR(50),
    amount     DECIMAL(182)
)
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 不是直接把整张表当成一个整体存放,而是先分区、再分桶,再把桶中的数据分布到集群节点上。

下一篇,我们继续解决建表时一定会遇到的问题:

明细模型、主键模型、聚合模型和更新模型到底有什么区别,一张业务表该选哪一种?