1. 问题现象
在 ClickHouse 中创建本地表和分布式表时,本地表创建成功,分布式表创建失败。
建表语句如下:
CREATE TABLE t_distributed_query_local ON CLUSTER two_shard
(
`id` UInt8,
`repo` UInt8
)
ENGINE = TinyLog;
CREATE TABLE t_distributed_query_all ON CLUSTER two_shard
(
`id` UInt8,
`repo` UInt8
)
ENGINE = Distributed(two_shard, default, t_distributed_query_all, rand());
执行第二个 SQL 时报错:
Code: 269, e.displayText() = DB::Exception:
Distributed table t_distributed_query_all looks at itself
(version 21.4.3.21 (official build))
客户端返回结果中可以看到两个节点都失败了:
┌─host──┬─port─┬─status─┬─error────────────────────────────────────────────┐
│ node3 │ 9977 │ 269 │ Distributed table t_distributed_query_all looks at itself │
│ node2 │ 9977 │ 269 │ Distributed table t_distributed_query_all looks at itself │
└───────┴──────┴────────┴──────────────────────────────────────────────────┘
服务端日志中也能看到 DDLWorker 在执行 ON CLUSTER 任务时报错:
DDLWorker: Processing task query-0000000012
CREATE TABLE default.t_distributed_query_all
ENGINE = Distributed(two_shard, default, t_distributed_query_all, rand())
DB::Exception: Distributed table t_distributed_query_all looks at itself
2. 问题原因
ClickHouse 官方文档中说明,Distributed 表引擎本身不保存数据,它只是一个分布式查询代理。查询分布式表时,ClickHouse 会把查询转发到集群中各个 shard 上的远端表,再合并结果。
Distributed 引擎的基本语法是:
Distributed(cluster_name, database_name, table_name[, sharding_key])
这里第三个参数 table_name 不是当前分布式表名,而是远端节点上真正存储数据的本地表名。
本例中已经创建了本地表:
t_distributed_query_local
但是创建分布式表时写成了:
ENGINE = Distributed(two_shard, default, t_distributed_query_all, rand())
也就是说,分布式表 t_distributed_query_all 指向的远端表也是 t_distributed_query_all。
这就形成了一个自引用:
t_distributed_query_all
-> Distributed(two_shard, default, t_distributed_query_all)
-> t_distributed_query_all
-> Distributed(two_shard, default, t_distributed_query_all)
如果 ClickHouse 允许这种表创建成功,后续查询就可能在分布式表之间不断转发,形成循环查询。因此 ClickHouse 在创建表时直接拦截,并抛出:
Distributed table t_distributed_query_all looks at itself
3. 正确写法
正确做法是让分布式表指向本地表。
本地表:
CREATE TABLE t_distributed_query_local ON CLUSTER two_shard
(
`id` UInt8,
`repo` UInt8
)
ENGINE = TinyLog;
分布式表:
CREATE TABLE t_distributed_query_all ON CLUSTER two_shard
(
`id` UInt8,
`repo` UInt8
)
ENGINE = Distributed(two_shard, default, t_distributed_query_local, rand());
关键区别就是第三个参数:
-- 错误
Distributed(two_shard, default, t_distributed_query_all, rand())
-- 正确
Distributed(two_shard, default, t_distributed_query_local, rand())
4. 分布式表和本地表的关系
创建 ClickHouse 分片表时,通常会有两类表:
| 表类型 | 示例 | 作用 |
|---|---|---|
| 本地表 | t_distributed_query_local |
每个节点上真实保存数据 |
| 分布式表 | t_distributed_query_all |
作为统一查询入口,把查询分发到各个 shard |
可以简单理解为:
用户查询 t_distributed_query_all
|
v
Distributed 引擎根据 two_shard 集群配置分发查询
|
v
node2.default.t_distributed_query_local
node3.default.t_distributed_query_local
|
v
汇总结果返回给客户端
所以分布式表不是数据表本身,而是访问本地表的一层代理。
5. 集群配置也需要检查
草稿中还记录了集群配置:
<two_shard>
<shard>
<replica>
<host>node3</host>
<port>9977</port>
</replica>
</shard>
<shard>
<replica>
<host>node2</host>
<port>9977</port>
</replica>
</shard>
</two_shard>
这个片段本身看起来像一个集群定义,但是在 ClickHouse 配置中它需要放到 <remote_servers> 下面,例如:
<clickhouse>
<remote_servers>
<two_shard>
<shard>
<replica>
<host>node3</host>
<port>9977</port>
</replica>
</shard>
<shard>
<replica>
<host>node2</host>
<port>9977</port>
</replica>
</shard>
</two_shard>
</remote_servers>
</clickhouse>
ClickHouse 官方集群部署文档中也说明,集群配置是在 <remote_servers> 配置块中定义的。
修改配置后,需要确认每台 ClickHouse 节点都加载到了相同的集群配置。
可以查看:
SELECT
cluster,
shard_num,
replica_num,
host_name,
port
FROM system.clusters
WHERE cluster = 'two_shard';
正常应该能看到:
two_shard node2 9977
two_shard node3 9977
草稿里的查询结果没有看到 two_shard:
select cluster,host_name from system.clusters;
test_cluster_two_shards
test_cluster_two_shards_internal_replication
test_cluster_two_shards_localhost
...
如果实际环境中也看不到 two_shard,需要检查:
- 配置是否放在
<remote_servers>下。 - 配置文件是否放到了 ClickHouse 能加载的位置。
- 所有节点是否都同步了配置。
- 是否重启或 reload 了 ClickHouse 配置。
- 查询
system.clusters的节点是否就是当前使用的节点。
可以尝试重新加载配置:
SYSTEM RELOAD CONFIG;
如果配置结构有问题,建议直接重启 ClickHouse 并查看启动日志。
6. 建议排查顺序
遇到这个错误时,建议按下面顺序排查。
6.1 检查 Distributed 第三个参数
先执行:
SHOW CREATE TABLE t_distributed_query_all;
重点看:
ENGINE = Distributed(two_shard, default, ?, rand())
第三个参数必须是真正存储数据的本地表,不应该是当前分布式表自己。
6.2 检查本地表是否存在
在每个节点检查:
EXISTS TABLE default.t_distributed_query_local;
或者:
SHOW TABLES FROM default LIKE 't_distributed_query_local';
如果本地表不存在,即使分布式表创建成功,后续查询也会报远端表不存在。
6.3 检查集群配置
SELECT cluster, host_name, port
FROM system.clusters
WHERE cluster = 'two_shard';
如果没有结果,先修复集群配置。
6.4 检查 ON CLUSTER DDL 队列
如果 ON CLUSTER 执行异常,可以查看分布式 DDL 队列:
SELECT
entry,
host,
status,
exception
FROM system.distributed_ddl_queue
ORDER BY entry DESC
LIMIT 10;
这个表可以帮助确认是哪台机器执行 DDL 失败,以及失败原因是什么。
7. 修复 SQL 示例
如果错误表已经创建失败,直接重新执行正确语句即可。
为了避免重复创建,可以加 IF NOT EXISTS:
CREATE TABLE IF NOT EXISTS t_distributed_query_local ON CLUSTER two_shard
(
`id` UInt8,
`repo` UInt8
)
ENGINE = TinyLog;
CREATE TABLE IF NOT EXISTS t_distributed_query_all ON CLUSTER two_shard
(
`id` UInt8,
`repo` UInt8
)
ENGINE = Distributed(two_shard, default, t_distributed_query_local, rand());
插入测试数据:
INSERT INTO t_distributed_query_all VALUES (1, 10), (2, 20), (3, 30);
查询:
SELECT *
FROM t_distributed_query_all;
再分别到本地表查看数据是否被分发到不同 shard:
SELECT *
FROM t_distributed_query_local;
8. 总结
这次异常的根因很明确:
Distributed 表 t_distributed_query_all 指向了自己。
错误写法:
ENGINE = Distributed(two_shard, default, t_distributed_query_all, rand())
正确写法:
ENGINE = Distributed(two_shard, default, t_distributed_query_local, rand())
创建 ClickHouse 分布式表时要记住一点:
Distributed 表是查询代理,本地表才是真正存储数据的表。
如果分布式表指向自己,就会形成循环引用,ClickHouse 会直接抛出 Distributed table ... looks at itself,防止后续查询进入无限转发。