ClickHouse查询涉及到多个分布式表,出现错误:DB::Exception: Double-distributed IN/JOIN subqueries is denied分析以及解决办法
ClickHouse查询涉及到多个分布式表,出现错误:DB::Exception: Double-distributed IN/JOIN subqueries is denied分析以及解决办法
1.场景
当使用ClickHouse查询涉及到多个分布式表时, 出现下面的错误:
SQL 错误 [288] [07000]: Code: 288. DB::Exception: Double-distributed IN/JOIN subqueries is denied (distributed_product_mode = 'deny'). You may rewrite query to use local tables in subqueries, or use GLOBAL keyword, or set distributed_product_mode to suitable value. (DISTRIBUTED_IN_JOIN_SUBQUERY_DENIED) (version 25.1.4.53 (official build)) , server ClickHouseNode [uri=http://xxx:8123/default, options={use_server_time_zone=false,use_time_zone=false}]@-696823690
2.分析
当查询涉及到多个分布式表时,特别是子查询中的IN或JOIN操作,如果子查询和主查询都涉及分布式表, ClickHouse为了防止潜在的性能问题,ClickHouse 会默认拒绝这种操作(
distributed_product_mode = 'deny'
), 这是为了防止跨多个分片(Shard)的笛卡尔积操作导致性能问题。上面的错误可能在两个分布式表之间进行了关联查询,导致了这个错误,这个错误与 ClickHouse 的分布式表查询模式distributed_product_mode
有关
举个例子:
SELECT * FROM distributed_table_A
WHERE id IN (SELECT id FROM distributed_table_B)
如果
distributed_table_A
和distributed_table_B
都是分布式表,且数据分布在多个分片节点上,这种查询就会被拒绝
3.解决方法
可能需要改写查询,使用本地表而不是分布式表,或者使用GLOBAL关键字,或者调整distributed_product_mode的设置
(1).使用本地表
有时候在查询的时候可能不太清楚分布式表和本地表的区别:比如,分布式表是逻辑上的,数据实际存储在本地表(分片)中。因此,在子查询中使用本地表可能更高效,也能避免这个问题,如果子查询的数据可以在一台节点上完成,直接在子查询中使用本地表(即实际存储数据的表,而非分布式表):
SELECT * FROM distributed_table_A
WHERE id IN (SELECT id FROM local_table_B)
(2).使用GLOBAL关键字
使用GLOBAL IN或GLOBAL JOIN时,ClickHouse会强制将子查询在一个节点上执行,然后将结果广播到其他节点,这样避免跨分片查询的问题。这时候就需要在子查询前加上GLOBAL关键字
SELECT * FROM distributed_table_A
WHERE id GLOBAL IN (SELECT id FROM distributed_table_B)
(3).调整配置
修改配置的方法,可能需要临时或永久调整distributed_product_mode,这里需要注意:修改配置可能会有性能影响,需谨慎操作,特别是在生产环境中。
例如:应用场景是在处理跨分片的数据关联,比如日志分析或用户数据查询。可能不太熟悉ClickHouse的分布式查询机制,所以需要更进一步的了解分布式查询机制后进行方法的选择,比如如果查询频率高,可能更适合调整配置;如果偶尔查询,用GLOBAL更方便
通过以下方式调整配置(需权衡性能和安全性):
临时调整(当前会话)
SET distributed_product_mode = 'allow';
永久调整(修改配置文件 config.xml
)
<distributed_product_mode>allow</distributed_product_mode>
可选模式:
deny
(默认):禁止跨分片子查询local
:将子查询中的分布式表替换为本地表global
:将子查询中的分布式表替换为全局临时表allow
:允许跨分片子查询(不推荐,可能导致性能问题)
4.应用场景示例
假设有两个分布式表
user_distributed
和order_distributed
,需要查询某个用户的订单
-- 错误写法(触发错误)
SELECT * FROM order_distributed
WHERE user_id IN (SELECT id FROM user_distributed WHERE name = 'Alice')
-- 正确写法(使用 GLOBAL)
SELECT * FROM order_distributed
WHERE user_id GLOBAL IN (SELECT id FROM user_distributed WHERE name = 'Alice')
5.总结
- 优先使用
GLOBAL
:适用于低频或一次性查询 - 优先使用本地表:适用于高频查询,需提前规划数据分布
- 谨慎修改
distributed_product_mode
:仅在完全理解风险后调整
更多推荐
所有评论(0)