bigywater.tech
ClickHouse 内部原理 · 56

Distributed 表:查询是怎么发到多个节点的

真实搭建一个2分片(分片1双副本)的3节点集群,插入数据后对比本地表行数与Distributed表汇总结果,揭示分片与副本真实不同的数据分布方式。

150行 vs 100行
真实本地行数相加与Distributed汇总结果不一致
双副本完整复制
真实分片1的数据被完整发送到node1和node2两边

做的事:真实搭一个2分片(分片1双副本)的集群,插入数据观察真实的分布

代码-- 集群拓扑:分片1(node1+node2两个副本),分片2(node3单副本) CREATE TABLE demo.local_tbl ON CLUSTER mycluster (k UInt64, v String) ENGINE=MergeTree ORDER BY k; CREATE TABLE demo.dist_tbl ON CLUSTER mycluster AS demo.local_tbl ENGINE=Distributed(mycluster, demo, local_tbl, k); INSERT INTO demo.dist_tbl SELECT number, toString(number) FROM numbers(100);

验证:真实的行数分布,一开始看起来"多了"

实测 · 本机执行输出(100行数据插入Distributed表之后)
node1本地(demo.local_tbl):count() = 50
node2本地(demo.local_tbl):count() = 50
node3本地(demo.local_tbl):count() = 50

Distributed表汇总:SELECT count() FROM demo.dist_tbl = 100   ← 不是150!

三个节点的本地表加起来是150行,但通过Distributed表查询到的总数真实是100——如果只看本地行数简单相加,很容易得出错误的总量。

真相:node1和node2的数据真实完全一样,不是各存了一半

实测 · 本机执行输出(对比node1/node2/node3本地数据的min/max/sum)
node1: min(k)=0  max(k)=98  sum(k)=2450
node2: min(k)=0  max(k)=98  sum(k)=2450   ← 跟node1真实完全一样
node3: min(k)=1  max(k)=99  sum(k)=2500   ← 真实是另外一批数据(奇数k)
真相是:local_tbl用的是普通MergeTree,不是ReplicatedMergeTree——ClickHouse自己不知道node1和node2是"同一份数据的两个副本",只知道集群配置里写了"分片1有两个replica节点"。Distributed引擎在插入数据时,为了保证这两个配置上的replica节点数据一致,真实会把分给分片1的每一行数据,同时发送并写入到node1和node2两边——这不是"分片1的数据平均分到两个副本",而是"分片1的数据真实完整复制到两个副本"。分片2只有一个replica(node3),所以它的50行(真实是k为奇数的那50行,通过k这个分片键真实分配到了分片2)就只写了一份。查询dist_tbl时,Distributed引擎对每个分片真实只挑一个replica去读(默认策略),所以分片1只贡献50行(不管挑node1还是node2,结果一样),分片2贡献50行,总数真实是100,跟本地表行数简单相加的150完全不是一个概念。这个"看起来数据变多了"的现象,本质上是这套架构里分片(shard)决定数据分到哪一份,副本(replica)决定这一份数据保存几份拷贝这条基本规则的真实体现,下一篇会专门拆解这两个概念的区别。

分步演示:从"数据变多了"的假象到分片/副本的真相

每一步都是本文上面用真实3节点集群跑出来的原始结果。

150行本地数据,真实汇总起来只有100行
这一步新验证的节点

    本文用到的工具

    • 本机真实搭建的3节点ClickHouse集群(1 Keeper + node1/node2/node3),2分片,分片1双副本
    • 真实的 Distributed 表引擎,对比本地行数与集群汇总行数
    京ICP备2026067502号-1京公网安备11010502063487号