Distributed 表引擎是 ClickHouse 集群中实现“跨分片查询”的关键组件,但它常常被误解为“真正的表”。事实上,它只是一个逻辑层的“查询路由视图”,理解这一点是避免性能陷阱和误用的核心。
本篇将深入详解 Distributed 表的本质、工作原理、常见误区与正确使用方式。
🧩 一、Distributed 表到底是什么?
✅ 本质:一个“查询代理”或“逻辑视图”
Distributed 表本身不存储数据!
它只是一个元数据定义,告诉 ClickHouse:
- 查询应该路由到哪些分片(shard)
- 每个分片上的本地表名
- 如何选择分片(通过分片键)
当查询 Distributed 表时,ClickHouse 会:
- 将查询分发到所有相关分片
- 在每个分片上执行查询(在本地表上)
- 将结果汇总并合并,返回给客户端
🔁 类比理解
| 类比 | 说明 |
|---|---|
| 数据库视图(View) | 不存数据,查询时动态执行 SQL |
| 负载均衡器(如 Nginx) | 接收请求,转发到后端服务 |
| 联邦查询(Federated Query) | 跨多个数据源查询 |
🎯 Distributed 表 = 分片路由 + 结果聚合
🏗️ 二、Distributed 表的创建语法
CREATE TABLE distributed_table_name
ENGINE = Distributed(
cluster_name, -- 集群名(在 config.xml 中定义)
database_name, -- 远程数据库名
table_name, -- 远程本地表名(如 user_log_local)
sharding_key -- 分片键(可选)
)
AS SELECT ...;
示例
CREATE TABLE user_log_all
ENGINE = Distributed(
'prod_cluster', -- 集群名
'default', -- 数据库
'user_log_local', -- 每个分片上的本地表
intHash32(user_id) -- 分片键:按 user_id 哈希分片
)
AS (user_id UInt32, event_date Date, action String);
🔍 三、Distributed 表的工作流程
客户端
│
↓
[ Distributed 表 user_log_all ]
│
↓
将查询广播到 prod_cluster 的所有分片
│
↓
[Shard 1] → 在 user_log_local 上执行查询
[Shard 2] → 在 user_log_local 上执行查询
│
↓
各分片返回中间结果
│
↓
Distributed 表汇总结果(如 SUM、GROUP BY 合并)
│
↓
返回最终结果给客户端
✅ 特点:
- 查询是并行执行的(MPP 架构)
- 最终聚合由 Distributed 表完成
- 网络开销存在(结果传输)
⚠️ 四、“SELECT * FROM distributed_table”的坑(严重性能问题)
❌ 错误用法
-- 危险!
SELECT * FROM user_log_all;
为什么是坑?
-
全表扫描所有分片
- 每个分片都要扫描
user_log_local全表 - I/O 和 CPU 压力巨大
- 每个分片都要扫描
-
网络传输爆炸
- 所有分片将全部原始数据发回查询节点
- 网络带宽成为瓶颈
-
内存压力大
- 查询节点需缓存所有中间结果
- 可能 OOM(内存溢出)
-
无法利用分区裁剪和索引
- 如果没有
WHERE条件,稀疏索引和分区无效
- 如果没有
🚨 在生产环境,
SELECT * FROM distributed_table是“自杀式查询”!
✅ 五、正确使用 Distributed 表的 6 大原则
1. ✅ 始终带上 WHERE 条件(利用分区和主键)
-- 好:走分区裁剪 + 索引
SELECT count(*)
FROM user_log_all
WHERE event_date = '2024-04-01' AND city = 'Beijing';
- 每个分片只扫描相关数据块
- 网络传输量小
2. ✅ 避免 SELECT *,只查所需列
-- 好
SELECT user_id, action, duration
FROM user_log_all
WHERE event_date = '2024-04-01';
-- 坏
SELECT * FROM user_log_all WHERE ...;
- 列式存储,只读所需列 → 减少 I/O 和网络
3. ✅ 聚合尽量在本地完成(两阶段聚合)
ClickHouse 会自动将聚合分为两阶段:
- 阶段1:每个分片上先聚合(
Partial aggregation) - 阶段2:Distributed 表汇总合并
-- 自动优化为两阶段
SELECT city, COUNT(*), AVG(duration)
FROM user_log_all
GROUP BY city;
✅ 效果:减少网络传输的数据量。
4. ✅ 合理设置分片键(sharding_key)
intHash32(user_id) -- 按用户哈希分片
rand() -- 随机分片
city -- 按城市分片(需配合哈希)
✅ 好处:
- 写入负载均衡
- 查询时可路由到特定分片(如果条件匹配)
5. ✅ 写入也通过 Distributed 表(推荐)
INSERT INTO user_log_all VALUES (...);
- 自动根据分片键路由到对应分片
- 无需手动指定写哪个本地表
6. ✅ 监控查询性能
-- 查看分布式查询状态
SELECT * FROM system.distributed_queues;
-- 查看慢查询
SELECT query, read_rows, query_duration_ms
FROM system.query_log
WHERE query LIKE '%user_log_all%'
ORDER BY query_duration_ms DESC;
📊 六、Distributed 表 vs 本地表:何时用哪个?
| 场景 | 推荐表类型 |
|---|---|
| 应用查询(跨分片) | Distributed 表 |
| 批量写入 | Distributed 表(或任意副本的本地表) |
| 高频点查(已知分片) | 直接查本地表(减少跳转) |
| 运维操作(如 OPTIMIZE) | 在每个节点操作本地表 |
| 物化视图写入目标 | 本地表(如 user_log_local) |
✅ 一般规则:
- 对外服务用
Distributed表- 内部维护用本地表
🧠 七、Distributed 表的高级参数
| 参数 | 说明 |
|---|---|
internal_replication | true:写入时只写一个副本(由复制引擎同步)false:写所有副本(不推荐) |
load_balancing | 负载均衡策略:random, nearest_hostname, in_order |
max_parallel_replicas | 在单分片内并行查询多个副本(用于大查询) |
✅ 八、总结:Distributed 表的核心认知
| 认知 | 正确认知 |
|---|---|
| ❌ 它是一个存储表 | ✅ 它是一个查询路由逻辑 |
❌ SELECT * 没问题 | ✅ SELECT * 是性能杀手,必须避免 |
| ❌ 查询总是快 | ✅ 性能取决于分片数、网络、聚合方式 |
❌ 可以频繁 OPTIMIZE | ✅ OPTIMIZE 应在本地表执行 |
| ❌ 写入必须指定分片 | ✅ 写 Distributed 表自动路由 |
🎯 记住一句话:
“Distributed 表是集群的‘门面’,不是‘仓库’。”
3651

被折叠的 条评论
为什么被折叠?



