ClickHouseNotes

第 17 章:分布式表

zjc 于 2026-01-17 发布

这是《ClickHouse 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 分布式表是分片集群的统一入口。它本身不保存业务数据,而是负责把写入路由到分片,把查询下发到各分片本地表,再合并结果。

17.1 架构

             Distributed table: events_all
                        |
       +----------------+----------------+
       |                |                |
    shard-1          shard-2          shard-3
 events_local      events_local      events_local
       |
  replica-1
  replica-2

本地表是真实存储,分布式表是路由和查询协调层。

17.2 创建本地表

在每个分片上创建:

CREATE TABLE analytics.events_local
(
    event_date Date,
    event_time DateTime,
    user_id UInt64,
    event_type LowCardinality(String),
    city_id UInt32,
    amount Decimal64(2)
)
ENGINE = ReplicatedMergeTree(
    '/clickhouse/tables/{shard}/events',
    '{replica}'
)
PARTITION BY toYYYYMM(event_date)
ORDER BY (event_date, event_type, city_id, user_id);

可以使用 ON CLUSTER

CREATE TABLE analytics.events_local
ON CLUSTER analytics_cluster
(
    event_date Date
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY event_date;

集群 DDL 要先在测试环境验证宏配置和权限。

17.3 创建分布式表

CREATE TABLE analytics.events_all
AS analytics.events_local
ENGINE = Distributed(
    analytics_cluster,
    analytics,
    events_local,
    cityHash64(user_id)
);

参数含义:

参数 含义
analytics_cluster 集群名
analytics 本地数据库
events_local 本地表
cityHash64(user_id) 分片键表达式

常用分片函数:

rand()
cityHash64(user_id)
cityHash64(user_id, event_date)
sipHash64(order_id)

17.4 写入路径

通过 Distributed 写入:

应用 -> 任一 ClickHouse 节点
     -> 计算目标分片
     -> 异步或同步转发
     -> 各分片本地表

优点是使用简单;缺点是写入节点会承担转发压力,并可能出现本地 buffer 数据。写入端直连分片更可控,但需要应用或代理层维护拓扑。

17.5 查询路径

客户端
  -> Distributed 表
  -> 每个 shard 选择副本
  -> 本地表并行执行
  -> 返回中间结果
  -> 发起节点合并

查看集群:

SELECT cluster, shard_num, replica_num, host_name, is_local
FROM system.clusters
WHERE cluster = 'analytics_cluster';

17.6 聚合语义

可下发聚合:

SELECT event_date, city_id, count(), sum(amount)
FROM analytics.events_all
WHERE event_date = today()
GROUP BY event_date, city_id;

每个分片先算局部结果,再由发起节点合并。

需要注意:

  1. countDistinct(user_id) 跨分片需合并集合;
  2. uniq(user_id) 可以合并近似状态;
  3. avg 不能直接对各分片平均值求平均;
  4. min/max/sum 可合并;
  5. LIMIT 可能需要各分片返回局部 TopN。

17.7 分布式 DDL

CREATE TABLE analytics.events_local ON CLUSTER analytics_cluster
(
    event_date Date,
    user_id UInt64
)
ENGINE = ReplicatedMergeTree('/clickhouse/tables/{shard}/events', '{replica}')
ORDER BY event_date;

查看队列:

SELECT cluster, query, host, status, exception_code
FROM system.distributed_ddl_queue
ORDER BY entry DESC
LIMIT 20;

集群 DDL 超时或部分节点失败时,要检查各节点实际表结构,不能只看返回结果。

17.8 负载均衡与副本

常用做法:

  1. 应用通过负载均衡访问 ClickHouse;
  2. Distributed 表选择分片副本;
  3. 查询节点和存储节点分离;
  4. 长查询和短查询隔离;
  5. 限流和排队。

如果发起节点承担所有合并和结果输出,它可能先成为瓶颈。大规模报表可以引入独立查询入口和资源池。

17.9 运维操作

查看远程文件系统缓存、分布式队列和 part:

SELECT database, table, is_blocked, error_count,
       data_files, bytes_to_send
FROM system.distribution_queue;

修复分布式表:

SYSTEM STOP DISTRIBUTED SENDS analytics.events_all;
SYSTEM START DISTRIBUTED SENDS analytics.events_all;

当写入积压时,先检查目标分片、网络、磁盘、Too many parts 和权限,而不是盲目重启。

17.10 常见错误

问题 原因
查询缺少数据 分片路由变化
数据倾斜 分片键选择不当
某分片慢 副本延迟或磁盘热点
DDL 部分失败 节点不可达或权限异常
写入队列积压 异步转发受阻
结果不一致 查询到延迟副本

本章小结

分布式表把多个分片的本地表组织成统一查询入口,分片键决定数据分布,副本提供可用性。使用时要理解写入转发、查询合并、分布式 DDL 和故障语义。分布式表不是高可用本身,稳定的集群来自分片、副本、监控和容量规划共同作用。

思考题

  1. Distributed 表保存业务数据吗?
  2. 如何选择分片键?
  3. 跨分片 avg 为什么不能直接平均?
  4. 分布式 DDL 部分失败如何排查?
  5. 应用直连分片和写 Distributed 表各有什么取舍?