bigywater.tech
ClickHouse 内部原理 · 60

分布式 JOIN 为什么容易变慢

真实用Distributed表JOIN一张只在单个节点有数据的本地维度表,揭示每个分片各用各的本地数据导致静默漏算的真实坑,并验证修复方式。

600→1200
真实修复后同样的JOIN查询结果翻倍
静默漏算无报错
真实数据不一致时ClickHouse不会给出任何警告

做的事:真实用Distributed表JOIN一张只在一个节点有数据的维度表

代码CREATE TABLE demo.dim_local ON CLUSTER mycluster (id UInt64, name String) ENGINE=MergeTree ORDER BY id; -- 真实只在node1插入维度数据,不做任何分发 INSERT INTO demo.dim_local SELECT number, concat('name',toString(number)) FROM numbers(2100); -- 从node1和node3分别发起同一条JOIN查询 SELECT count() FROM demo.dist_tbl t INNER JOIN demo.dim_local d ON t.k=d.id;

验证:真实不管从哪个节点发起,结果都是同样的600

实测 · 本机执行输出
从node1发起:SELECT count() FROM demo.dist_tbl JOIN demo.dim_local ... → 600
从node3发起:同样的查询                                              → 600  ← 真实一样

真实拆解:
node1本地(分片1) JOIN node1本地dim_local(有2100行真实数据) → 600  ← 真实正确的那部分
node3本地(分片2) JOIN node3本地dim_local(0行,没有数据)   → 0    ← 真实缺失的那部分

不管从node1还是node3发起,600这个数字真实只包含了分片1的贡献——因为分片2(node3)在执行它自己那部分的JOIN时,用的是它自己本地的dim_local,而node3本地这张表压根没有数据,贡献永远是0,跟查询从哪个节点发起完全无关。

真实的坑:非Distributed的JOIN右表,每个分片各用各的本地数据

这正是分布式JOIN真实最容易踩、也最容易被忽略的坑:demo.dist_tbl是Distributed表,查询会真实被拆解成"每个分片各自执行自己的那部分";但JOIN右边的demo.dim_local是一张普通的本地表,ClickHouse不会自动把它的数据广播/分发给每个分片,而是让每个分片各自用它本地磁盘上那份dim_local数据去做JOIN——如果这份维度数据只在某一个节点插入过(本文的node1),其余分片本地的同名表就是空的,JOIN在那些分片上永远匹配不到任何东西,而且这个错误不会报错、不会有任何提示,只是静默地漏算,非常容易被误以为是正常结果。真实的修复很直接:把dim_local的数据真实复制到每一个分片节点(本文把同样的2100行也插入了node3),之后同样的JOIN查询真实变成了1200,且从node1和node3发起结果一致——因为现在每个分片本地都有完整的维度数据了。这也解释了"分布式JOIN容易变慢"这个说法的真实来源:为了让每个分片JOIN右表时都能拿到完整数据,业界通常的两种真实做法是——小表(比如本文的维度表)真实复制到每一个分片节点(用空间换正确性和速度,这也是很多系统"broadcast join"的真实思路),或者像本系列下一篇会验证的GLOBAL IN/GLOBAL JOIN那样,让发起节点先把右表数据算好,再真实广播给所有分片——两种做法本质上都是在解决同一个问题:JOIN右表的数据,必须让每个分片都能访问到完整、一致的版本,否则结果真实会像本文这样悄悄错误。

分步演示:从静默漏算到真实修复的完整过程

每一步都是本文上面用真实分布式JOIN跑出来的原始结果。

600变1200:分布式JOIN真实的静默漏算与修复
这一步新验证的状态

    本文用到的工具

    • 本机真实的3节点ClickHouse集群,真实的Distributed表JOIN非Distributed本地表
    • 真实验证并修复了数据分布不一致导致的静默漏算问题
    京ICP备2026067502号-1京公网安备11010502063487号