使用Colocate Group优化Join

更新时间:
复制 MD 格式

分布式Join通常需要跨节点Shuffle数据,当表规模增大时Shuffle开销会成为性能瓶颈。Colocate Join通过将频繁关联的表按相同规则分布到相同节点,使Join在本地完成,完全避免网络传输。本文介绍如何创建和管理Colocate Group,以及如何验证Colocate Join是否生效。

Colocate Join原理

常规分布式Join需要将两张表的数据按Join Key重新Hash分发到相同节点,这个Shuffle过程涉及大量网络传输,在大表场景下往往是查询的性能瓶颈。

Colocate Join的思路是从建表阶段就解决数据分布问题:如果两张表按相同的分桶键和分桶数进行Hash分布,那么相同Key值的数据天然在同一个BE节点上,Join时直接本地执行即可。

SelectDB通过Colocate Group管理这种数据共置关系。同一Group内的表保证:

  • 相同Hash值的Bucket分布在相同的BE节点。

  • 节点变更时,组内所有表的Bucket映射统一调整。

前提条件

同一Colocate Group内的表必须满足以下条件,否则建表或修改会报错:

约束项

要求

说明

分桶键

数据类型和数量一致

列名可以不同,但数据类型必须完全相同(如不能一张表用INT另一张用BIGINT)

分桶数量

完全一致

所有表的BUCKETS数必须相同,不能使用BUCKETS AUTO

不满足条件时建表会报错,例如分桶键类型不匹配时提示Colocate tables distribution columns must have the same data type

创建Colocate

建表时通过colocate_with属性指定Group名称,同名即同组:

-- 订单表,以user_id分桶,加入group_user
CREATE TABLE orders (
    order_id BIGINT,
    user_id BIGINT,
    product_id BIGINT,
    order_date DATE,
    amount DECIMAL(10,2)
) DISTRIBUTED BY HASH(user_id) BUCKETS 4
PROPERTIES (
    "colocate_with" = "group_user"
);

-- 用户表,同样以user_id分桶,加入同一Group
CREATE TABLE users (
    user_id BIGINT,
    username VARCHAR(100),
    city_id BIGINT,
    vip_level INT
) DISTRIBUTED BY HASH(user_id) BUCKETS 4
PROPERTIES (
    "colocate_with" = "group_user"
);

两张表指定相同的colocate_with值即属于同一Colocate Group。首张表创建时Group自动建立,后续表加入时系统检查约束是否满足。

查看Colocate Group状态

通过SHOW PROC '/colocation_group'查看当前所有Group信息:

SHOW PROC '/colocation_group';

输出示例:

+-------------------------------+----------------------------+-----------+------------+-------------------+---------+----------+----------+
| GroupId                       | GroupName                  | TableIds  | BucketsNum | ReplicaAllocation | DistCols| IsStable | ErrorMsg |
+-------------------------------+----------------------------+-----------+------------+-------------------+---------+----------+----------+
| 1778568404439.1778568404759   | test_query_opt.group_user  | 10749,10776| 4         | tag.location.default:1| bigint | true     |          |
+-------------------------------+----------------------------+-----------+------------+-------------------+---------+----------+----------+

关键字段说明:

  • GroupName:格式为{数据库名}.{Group名称}

  • TableIds:Group内的表ID列表。

  • BucketsNum:组内统一的分桶数。

  • IsStabletrue表示数据分布已平衡,Colocate Join可正常使用;false表示正在重平衡(通常因BE节点增减触发),此时Colocate Join会退化为Shuffle Join。

验证Colocate Join生效

Join的两张表属于同一Colocate GroupJoin Key为分桶键时,优化器自动选择Colocate Join。通过EXPLAIN确认:

EXPLAIN SELECT o.order_id, u.username, o.amount
FROM orders o JOIN users u ON o.user_id = u.user_id
WHERE o.order_date >= '2024-01-01';

-- 在输出中查找join op字段:
-- join op: INNER JOIN(COLOCATE[])[]  表示Colocate Join已生效
-- 如果显示 INNER JOIN(PARTITIONED) 或 INNER JOIN(BROADCAST),说明退化了

以下情况Colocate Join会退化为ShuffleBroadcast:

  • Join Key不是分桶键。例如ordersusers虽在同一Group,但如果按product_id关联则无法使用Colocate。

  • GroupIsStablefalse,数据正在重平衡。

  • 通过Session变量手动关闭了Colocate计划:SET disable_colocate_plan = true

管理Colocate Group

通过ALTER TABLE可以随时调整表的Group归属:

-- 将表加入一个Group(Group不存在则自动创建)
ALTER TABLE orders SET ("colocate_with" = "group_user");

-- 将表从一个Group移到另一个Group
ALTER TABLE orders SET ("colocate_with" = "group_new");

-- 将表移出Colocate Group
ALTER TABLE orders SET ("colocate_with" = "");

修改colocate_with时系统会检查目标Group的约束(分桶键类型、分桶数),不满足则报错。

删除Group内的表时,表先进入回收站。当Group内最后一张表从回收站彻底删除后,Group才自动清除。即使Group内所有表都已删除,在彻底清除前GroupSchema信息(分桶键类型等)仍保留,新加入的表仍需满足原有约束。

典型场景

星型模型中事实表与维度表Join

将事实表与高频关联的维度表放入同一Colocate Group,以关联键作为分桶键:

-- orders和users均以user_id分桶,Join走Colocate无需Shuffle
SELECT u.username, SUM(o.amount) AS total_sales
FROM orders o
JOIN users u ON o.user_id = u.user_id
WHERE o.order_date BETWEEN '2024-01-01' AND '2024-06-30'
GROUP BY u.username;

Colocate Join只在Join Key等于分桶键时触发。如果还需要按product_id关联products表,该Join不走Colocate。对于多条高频Join路径,需要为不同的Join Key分别创建Colocate Group。

实时数仓多表关联

实时导入的多张明细表如果经常按用户维度关联分析,将它们统一按用户ID分桶并加入同一Group,可避免大规模数据的网络Shuffle。

使用建议

  • 分桶键应选频繁Join的关联列,而非分区列。

  • 合理规划分桶数量。过少导致数据倾斜,过多增加元数据开销,建议单个Bucket数据量在100MB~1GB之间。

  • 同一Group内表数量不宜过多,建议不超过10张。

  • 节点扩缩容后关注IsStable状态,待恢复true后再执行性能敏感的查询。