bigywater.tech
ClickHouse 内部原理 · 61

GLOBAL IN 和普通 IN 的区别

真实用只在单个节点有数据的维度表对比普通IN和GLOBAL IN,验证GLOBAL IN真实如何通过广播机制避开分布式JOIN同样的静默漏算问题。

50 vs 100
真实普通IN和GLOBAL IN在同样数据分布下的结果差异
广播非免费
真实GLOBAL IN的正确性代价是网络广播开销

做的事:真实用只在一个节点有数据的维度表,测普通IN和GLOBAL IN

代码-- 真实清空node3本地的dim_local,只剩node1有完整数据 TRUNCATE TABLE demo.dim_local; -- 在node3上执行 -- 普通IN:子查询会在哪里执行? SELECT count() FROM demo.dist_tbl WHERE k IN (SELECT id FROM demo.dim_local WHERE id < 500); -- GLOBAL IN SELECT count() FROM demo.dist_tbl WHERE k GLOBAL IN (SELECT id FROM demo.dim_local WHERE id < 500);

验证:真实一倍的差距,50 vs 100

实测 · 本机执行输出(node3本地dim_local已清空,只有node1有数据)
普通IN :SELECT count() FROM demo.dist_tbl WHERE k IN (...)         → 50
GLOBAL IN:SELECT count() FROM demo.dist_tbl WHERE k GLOBAL IN (...)  → 100

跟上一篇《分布式JOIN为什么容易变慢》验证的现象真实是同一个根源:普通IN子查询会被真实下推到每个分片各自执行,分片2(node3)执行这个子查询时用的是它自己本地(已清空)的dim_local,子查询结果是空集,所以分片2对WHERE k IN (...)的判断全部为假,真实一行都没匹配上,最终只有分片1的50行被算进了结果。

GLOBAL IN真实换了一种执行方式,从根源上避开了这个坑

GLOBAL IN真实的执行方式跟普通IN完全不同:子查询(SELECT id FROM demo.dim_local WHERE id < 500)真实只在发起查询的那个节点执行一次,用的是发起节点自己本地能访问到的dim_local数据(本文场景下发起查询的是node1,它本地真实有完整的维度数据);算出结果集之后,ClickHouse真实会把这份结果集当作一个临时表,通过网络广播给所有涉及的分片,各个分片用这份广播来的、完全一致的数据去做真正的IN判断——这样不管维度表本身的物理数据分布在哪个节点,只要发起查询的那个节点能访问到正确、完整的数据,所有分片就都能拿到同一份正确的判断集合。这也是为什么本文的GLOBAL IN能得到正确的100,而普通IN只能得到50——普通IN依赖"每个分片本地都得有正确的数据"这个前提(上一篇验证过,这个前提在数据分布不均时真实会被破坏),GLOBAL IN则是从执行方式上换了一条路:把"保证每个分片都能看到正确数据"这件事,从"物理复制到每个分片"改成了"运行时用网络广播一次性分发"。代价也很直接——广播这份结果集本身需要真实的网络传输和临时存储,如果子查询结果集很大,GLOBAL IN/GLOBAL JOIN这份广播的开销也会真实变大,不是没有代价的免费修复。

分步演示:从普通IN的漏算到GLOBAL IN的真实修复

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

50 vs 100:GLOBAL IN真实的广播修复了什么
这一步新验证的写法

    本文用到的工具

    • 本机真实的3节点ClickHouse集群,真实对比 IN 与 GLOBAL IN 在数据分布不均时的行为差异
    京ICP备2026067502号-1京公网安备11010502063487号