基于本文回答
0
评论

详细对比 Doris 支持的四种 Join 执行方式:Broadcast Join、Shuffle Join、Bucket Shuffle Join 和 Colocate Join

在 Apache Doris 中,Join 查询的性能很大程度上取决于数据的分布和传输方式。为了在分布式环境下实现高效的关联查询,Doris 支持四种不同的 Join 执行方式:Broadcast JoinShuffle JoinBucket Shuffle JoinColocate Join

以下是对这四种 Join 方式的原理、触发条件、优缺点及适用场景的详细对比和分析。


一、 四种 Join 方式的原理与特点

1. Broadcast Join (广播连接)

  • 原理:将 Join 的右表(通常是小表,称为 Build Table)的数据广播(复制)到含有左表(大表,称为 Probe Table)数据的所有 BE 节点上。每个 BE 节点在本地内存中构建哈希表,然后与本地的左表数据进行 Join。
  • 数据移动:左表不动,右表全量广播。
  • 网络开销O(右表大小×BE 节点数)O(\text{右表大小} \times \text{BE 节点数})

2. Shuffle Join (分区连接)

  • 原理:当两张表都比较大时,Doris 会根据 Join Key 的 Hash 值,将两张表的数据同时重新分发(Shuffle)到各个 BE 节点上。相同 Join Key 的数据会被发送到同一个 BE 节点,然后在本地进行 Join。
  • 数据移动:左右两表都需要网络传输(重新分发)。
  • 网络开销O(左表大小+右表大小)O(\text{左表大小} + \text{右表大小})

3. Bucket Shuffle Join (分桶哈希连接)

  • 原理:这是 Doris 针对特定场景的优化。如果左表的分桶键(Bucket Key)正好是 Join Key,那么左表的数据就不需要 Shuffle(因为相同 Join Key 的数据已经天然在同一个 BE 节点上了)。Doris 只需将右表的数据按照左表的分桶规则进行 Shuffle,发送到对应的 BE 节点进行本地 Join。
  • 数据移动:左表不动,右表根据左表的分桶规则进行 Shuffle。
  • 网络开销O(右表大小)O(\text{右表大小})

4. Colocate Join (同分布连接)

  • 原理:最极致的优化。通过在建表时指定相同的 Colocate Group,保证两张表(或多张表)具有相同的分桶键、相同的分桶数,并且相同分桶的数据物理上存储在相同的 BE 节点上。在 Join 时,Doris 直接在本地节点进行 Join,完全没有网络传输。
  • 数据移动零网络传输,全本地化计算。
  • 网络开销00

二、 核心维度对比表

维度 Broadcast Join Shuffle Join Bucket Shuffle Join Colocate Join
网络开销 中/高(随 BE 节点数线性增长) 高(双表网络传输) 低(仅单表网络传输) 极低(零网络传输)
内存开销 高(每个 BE 需容纳右表全量数据) 中(节点仅容纳分片数据) 中(节点仅容纳分片数据) 低(全本地化流式处理)
触发前提条件 右表足够小,能放入单 BE 内存 无特殊限制(通用机制) Join Key 必须包含左表的分桶键 1. 两表在同一个 Colocate Group
2. Join Key 必须是分桶键
适用数据规模 大表 \Join 极小表 (如维度表) 大表 \Join 大表 (无关联特征) 大表 \Join 中/大表 大表 \Join 大表 (高频关联)
对性能的影响 节点数多且右表偏大时,网络会成为瓶颈 容易受网络带宽限制,在大数据量下延迟高 相比 Shuffle Join 性能提升近一倍 性能最高,延迟最低,支持高并发
维护/设计成本 无(优化器自动选择) 无(优化器自动选择) 较低(建表时合理设计左表分桶键) 较高(建表时需强制绑定 Group,限制后期扩容/数据重分布)

三、 深入对比与场景分析

1. 为什么 Bucket Shuffle Join 优于 Shuffle Join?

  • 减少了一半的网络传输:在标准 Shuffle 中,A 表和 B 表都要跨网络传输。而 Bucket Shuffle 中,A 表(左表)因为分桶键就是 Join Key,它已经在正确的节点上了,所以只有 B 表需要走网络。
  • 减少了 CPU 开销:左表不需要重新计算 Hash 值和序列化,节省了 CPU。
  • 适用场景:用户画像表(以 user_id 分桶)关联行为明细表(以 user_id 分桶)。

2. Colocate Join 的威力与代价

Colocate Join 是 Doris 性能最强的 Join 方式,但它是有代价的:

  • 建表约束
    sql
    -- 表1
    CREATE TABLE table1 (k1 INT, v1 INT)
    DISTRIBUTED BY HASH(k1) BUCKETS 10
    PROPERTIES("colocate_with" = "group_a");
    
    -- 表2(必须与表1分桶键类型、分桶数、Group 名字完全一致)
    CREATE TABLE table2 (k1 INT, v2 INT)
    DISTRIBUTED BY HASH(k1) BUCKETS 10
    PROPERTIES("colocate_with" = "group_a");
  • 扩容影响:当集群扩容时,Doris 的 Colocate Manager 会在后台进行数据重平衡(Rebalance),以确保相同 Bucket 的数据仍然迁移到同一个新 BE 上。这期间可能会对写入性能产生短暂影响。
  • 适用场景:数仓中的双主表关联,例如订单表和订单明细表(都按 order_id 分桶),或者用户表和用户属性表。

四、 Doris 优化器(Nereids)的选择策略

在实际查询中,用户通常不需要手动指定使用哪种 Join(尽管可以通过 /*+ BROADCAST *//*+ SHUFFLE */ 的 Hint 来强制指定)。Doris 的新一代查询优化器(Nereids)会基于CBO(基于代价的优化)自动做出最优选择,选择逻辑大致如下:

  1. 检查是否满足 Colocate Join:如果两表属于同一 Colocate Group,且 Join 条件匹配分桶键,直接使用 Colocate Join。
  2. 检查是否满足 Bucket Shuffle Join:如果不满足 Colocate,但左表分桶键等于 Join Key,且右表大小合适,优先选择 Bucket Shuffle Join。
  3. 估算 Broadcast 与 Shuffle 的代价
    • 如果右表的数据量(经过 Filter 过滤后的行数和数据大小)非常小,优化器会选择 Broadcast Join
    • 如果右表数据量较大,或者 BE 节点数非常多导致 Broadcast 传输总量超过了 Shuffle,优化器会选择 Shuffle Join

五、 总结与设计建议

  • 数仓设计黄金法则:在设计 Doris 表结构时,尽量将高频作为 Join Key 的列(如 user_id, device_id, order_id)设为表的分桶键(Distribution Key)。这样即使无法实现 Colocate Join,也能白嫖到 Bucket Shuffle Join 的性能红利。
  • 核心核心业务表:对于业务上最核心、关联最频繁的两三张超大表,务必在建表时使用 colocate_with 属性将它们关联在一起,使用 Colocate Join
  • 维度表:对于配置表、字典表等小表,无需过多考虑分布,Doris 会自动通过 Broadcast Join 完美解决。
右滑查看面试常问