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:仅在完全理解风险后调整
Logo

更多推荐