前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ClickHouse分布式IN & JOIN 查询的避坑指南

ClickHouse分布式IN & JOIN 查询的避坑指南

作者头像
Nauu
修改于 2020-04-28 16:28:50
修改于 2020-04-28 16:28:50
10.1K0
举报

当数据表包含多个分片的时候,我们需要将普通的本地查询转换为分布式查询。当然,这个转换动作是不需要用户自己进行的,在ClickHouse里面会由Distributed表引擎代劳。

Distributed表引擎的定位就好比是一个分表的中间件,它本身并不存储数据,而是分片的代理,能自动的将SQL查询路由到每个分片。

对于分片概念、定义方式以及相关表引擎作用等内容,这里就不再赘述了,我在ClickHouse这本书中对它们都有过详细的论述。

总而言之,分布式查询是面向Distributed表引擎的,而Distributed与分片表的关系如下图所示:

一种约定俗成的命名方式,是将Distributed表附带_all后缀;本地分片附带_local后缀,以示区分。

当我们面对Distributed表引擎查询的时候,它主要为我们做了3件事情:

  1. 发起远程调用,根据集群的配置信息,从当前节点向远端分片发起Remote远程查询调用
  2. 分布式表转本地表,在发送远程查询时,将SQL内的 _all表 转成 _local表
  3. 合并结果集,合并由多个分片返回的数据

假设Distributed表test_all映射了两个分片,它们分布在CH5和CH6两个节点,那么在CH5节点执查询SELECT * FROM test_all 的执行计划会是下面的这个样子:

其中,Remote远程查询 和 One本地查询是并行的,所以图中归为了一个步骤。可以看到,面向Distributed表引擎查询,就自动的完成了整个分布式查询的过程。

是不是这样就高枕无忧了呢?

显然不是,铺垫了1000字,现在终于要进入正文了,哦也。

在大多数时候,面向Distributed表的SQL写法与本地查询没有多大区别。但当我们执行 IN 或者 JOIN 查询的时候,一不小心就容易掉到坑里,因为这些查询子句会面对多张数据表。

为了便于演示,我们简化一下场景,用一个自查询的IN子句来解释说明,假设一张表的数据如下:

代码语言:sql
AI代码解释
复制
SELECT * FROM test_query_local
┌─id─┬─repo─┐
│  1100 │
│  2100 │
│  3100 │
│  3200 │
│  4200 │
└────┴──────┘

现在有一个统计的需求,找到同时拥有repo = 100repo = 200的个数,那么它的查询SQL可能是下面这个样子

代码语言:sql
AI代码解释
复制
SELECT uniq(id) FROM test_query_local WHERE repo = 100 
AND id IN (SELECT id FROM test_query_local WHERE repo = 200)

这条语句目前在单机执行是没有问题的,id为3的数据同时拥有2个repo:

代码语言:sql
AI代码解释
复制
┌─uniq(id)─┐
│        1 │
└──────────┘

现在模拟分布式的场景,把这张表进行分片操作,将它们分布到CH5和CH6两个节点,且每个节点的数据数据如下:

代码语言:sql
AI代码解释
复制
CH5节点 test_query_local
┌─id─┬─repo─┐
│  1100 │
│  2100 │
│  3100 │
└────┴──────┘
CH6节点 test_query_local
┌─id─┬─repo─┐
│  3200 │
│  4200 │
└────┴──────┘

接着使用 分布式表 test_query_all 映射这2个分片。

那么,刚才的那条SQL应该怎么改?

  • 第一种改法

将本地表 test_query_local 改成 分布式表 test_query_all

代码语言:sql
AI代码解释
复制
ch5.nauu.com :) SELECT uniq(id) FROM test_query_all WHERE repo = 100 AND id IN (SELECT id FROM test_query_local WHERE repo = 200)

SELECT uniq(id)
FROM test_query_all
WHERE (repo = 100) 
AND (
 id IN 
   (    
    SELECT id    
    FROM test_query_local    
    WHERE repo = 200
   )
)
┌─uniq(id)─┐
│        0 │
└──────────┘
1 rows in set. Elapsed: 0.009 sec.

你会发现返回的数据不对,进一步检查,原因是由 IN 子句引起的,因为它还在使用本地表 test_query_local

这是什么原理呢?我们看下面这张图就明白了

分布式查询将 _all 表转 _local之后,在两个分片最终执行的语句是这样的:

代码语言:sql
AI代码解释
复制
SELECT uniq(id) FROM test_query_local WHERE repo = 100 
AND id IN (SELECT id FROM test_query_local WHERE repo = 200)

由于分片的数据分布是不同的,所以数据没有查全。

  • 第二种改法

在有了刚才的经验之后,现在把 IN 子句也替换成 _all 分布式表:

代码语言:sql
AI代码解释
复制
ch5.nauu.com :) SELECT uniq(id) FROM test_query_all WHERE repo = 100 AND id IN (SELECT id FROM test_query_all WHERE repo = 200)

SELECT uniq(id)
FROM test_query_all
WHERE (repo = 100) 
AND (
id IN (    
    SELECT id    
    FROM test_query_all    
    WHERE repo = 200
    )
)
┌─uniq(id)─┐
│        1 │
└──────────┘

从返回结果来看,这次好像没问题了。

为什么这样能返回正确的结果呢? 如下图所示:

站在CH5节点的视角,在SQL语句 _all 转 _local后,在CH5本地会执行下面的语句:

代码语言:sql
AI代码解释
复制
SELECT uniq(id) FROM test_query_local WHERE repo = 100 
AND id IN (SELECT id FROM test_query_all WHERE repo = 200)

注意,IN 子句此时是分布式表 test_query_all,所以它又转成了下面的形式,分别在CH5本地和CH6远端执行:

代码语言:sql
AI代码解释
复制
SELECT id FROM test_query_local WHERE repo = 200

讲到这里就应该很清楚了,因为 IN子句 单独发起了一次分布式查询,所以数据查不全的问题被解决了。

还有什么"坑" 吗? 当然有啦 !!

现在站在CH6节点的视角,SQL在CH5被 _all 转 _local后,会向CH6节点发起远程查询调用。在CH6本地将同样会执行下面的语句:

代码语言:sql
AI代码解释
复制
SELECT uniq(id) FROM test_query_local WHERE repo = 100 
AND id IN (SELECT id FROM test_query_all WHERE repo = 200)

注意 IN 子查询,由于它是 分布式表 test_query_all,所以它又会向集群内其他分片发起分布式查询,如下图所示:

这就是分布式查询的放大问题,放大次数是 N的平方(N = 分片数量)。所以说,如果一张表有10个分片,那么一次分布式 IN 查询的背后会涉及100次查询,这显然是不可接受的。

  • 第三种改法

查询放大怎么解决呢? ClickHouse为我们提供了解决方案,继续改造刚才的语句,增加 GLOBAL修饰符:

代码语言:sql
AI代码解释
复制
SELECT uniq(id) FROM test_query_all WHERE repo = 100 
AND id GLOBAL IN (SELECT id FROM test_query_all WHERE repo = 200)

增加了 GLOBAL 之后查询会有什么变化呢?

在使用了 GLOBAL 之后,整个分布式查询的流程又发生了变化,我们看下面这张图:

首先,GLOBAL 修饰的子句,单独进行了一次分布式查询;

接着,将子句的结果汇总后,用内存临时表保存;

最后,直接将临时表分发至每个分片节点,从而避免了查询放大的问题。

  • 关于JOIN查询

对于分布式JOIN查询而言,其执行逻辑和 IN查询是一样的,它们唯一的区别是分发的语句不同,例如:

当执行 IN子句的时候,是将IN子句提取,发起分布式查询:

代码语言:sql
AI代码解释
复制
GLOBAL IN (SELECT id FROM test_query_all WHERE repo = 200)

IN子句 _all 转 _local,分发到每个分片执行,再汇总:

代码语言:sql
AI代码解释
复制
#分布式执行
SELECT id FROM test_query_local WHERE repo = 200

当执行JOIN子句的时候,是将右表提取,发起分布式查询:

代码语言:sql
AI代码解释
复制
SELECT * FROM test_query_all AS t1 GLOBAL JOIN test_query_all AS t2 ON t1.id = t2.id

右表 _all 转 _local,分发到每个分片执行,再汇总:

代码语言:sql
AI代码解释
复制
#分布式执行
SELECT id, repo FROM default.test_query_local

所以分布式JOIN查询我就不再演示图例了,参照IN子句的即可。

好了,现在总结一下,当执行分布式JOIN 或者IN 查询的时候,会碰到几种问题:

  1. 查询不全,由于分片的数据不均,会出现查询数据不全的问题,所以JOIN表IN子句 也要使用 _all 分布式表;
  2. 查询放大,由于JOIN表 IN子句 也是 _all 分布式表,所以每个分片又会向其他远端的分片发起分布式查询,最终的查询次数是 N 的平方(N=分片数量);
  3. 解决思路,使用 GLOBAL IN GLOBAL JOIN 可以避免查询放大的问题。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 ClickHouse的秘密基地 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【ClickHouse 极简教程】分布式下的 IN/JOIN 及 GLOBAL关键字
从这个实现中可以看出,如果right_table的数据量超过单机可用内存空间的限制,则JOIN操作无法完成。通常,两表JOIN时,将较小表作为right_table.
一个会写诗的程序员
2021/12/16
2.6K0
【ClickHouse 极简教程】分布式下的 IN/JOIN 及 GLOBAL关键字
ClickHouse使用姿势系列之分布式JOIN
JOIN操作是OLAP场景无法绕开的,且使用广泛的操作。对ClickHouse而言,非常有必要对分布式JOIN实现作深入研究。
fastio
2021/06/03
5.1K0
ClickHouse 分布式集群的安装、使用
ClickHouse是俄罗斯的Yandex于2016年开源的列式存储数据库(DBMS),主要用于在线分析处理查询(OLAP),能够使用SQL查询实时生成分析数据报告。适合巨量数据环境下用户数据查询、数据分析等工作。ClickHouse 简称为 CH,是近2年日益火起来的一款类数据库分析工具。
南非骆驼说大数据
2020/12/13
3.5K0
故障分析 | ClickHouse 集群分布式 DDL 被阻塞案例一则
登录该节点查看show processlist,正在执行1个分布式ddl,该ddl已经运行100多个小时,应该是卡住了。
爱可生开源社区
2022/05/05
2.1K0
ClickHouse 集群部署(不需要 Zookeeper)
(1)安装 ClickHouse Server 和 ClickHouse Client
用户1148526
2024/04/18
8.6K2
clickhouse分布式查询报错Double-distributed IN/JOIN subqueries is denied
clickhouse从单机切换到分布式,查询的sql也需要跟随变化,最近线上遇到不少问题,这里给大家总结一下,给遇到类似问题同学提供一些帮助。
公众号-利志分享
2022/04/25
2.7K0
Clickhouse 分布式表&本地表 &ClickHouse实现时序数据管理和挖掘
◆ 一、CK 分布式表和本地表 (1)CK是一个纯列式存储的数据库,一个列就是硬盘上的一个或多个文件(多个分区有多个文件),关于列式存储这里就不展开了,总之列存对于分析来讲好处更大,因为每个列单独存储,所以每一列数据可以压缩,不仅节省了硬盘,还可以降低磁盘IO。 (2)CK是多核并行处理的,为了充分利用CPU资源,多线程和多核必不可少,同时向量化执行也会大幅提高速度。 (3)提供SQL查询接口,CK的客户端连接方式分为HTTP和TCP,TCP更加底层和高效,HTTP更容易使用和扩展,一般来说HTTP足矣,社
IT大咖说
2022/03/25
4.4K0
分布式物化视图在clickhouse如何实现?
物化视图在数据层面做指标大宽表有着举足轻重的作用,分布式物化视图是对物化视图存储的数据进行分布式读取。
公众号-利志分享
2022/04/25
3.3K1
一文入门 | 性能凶悍的开源分析数据库ClickHouse
ClickHouse是一个开源的,面向列的MPP架构数据分析数据库(大规模并行处理),由俄罗斯Yandex为OLAP和大数据用例创建。
灵雀云
2022/06/06
3.6K0
一文入门 | 性能凶悍的开源分析数据库ClickHouse
ClickHouse 实战笔记 第04期:ClickHouse 高可用集群搭建
在海量数据的场景下,单节点的 CH 可能不能满足我们的需求了,因此可以考虑使用 CH 集群,从而解决单节点存储和查询的瓶颈。
数据库交流
2022/04/25
4.9K0
ClickHouse 实战笔记 第04期:ClickHouse 高可用集群搭建
有料-clickhouse单机的增删查询实现方案和clickhouse分布式部署的增删查改实现方案
关于增删查改,很多同学都可能觉得这么简单的东西我还不会吗?嗯,大家是觉得简单,但是关于clickhouse的增删查改可还是值得深入了解一下,了解了他们的使用,对数据开发者来说,能避免不少的问题。
公众号-利志分享
2022/04/25
4760
ClickHouse案例:查询结果不一致
某用户反馈其使用的ClickHouse集群同样的查询返回了不同的结果,是否是ClickHouse数据不能够保证一致性,还是集群有问题。
Yannic
2020/11/16
14.3K1
ClickHouse案例:查询结果不一致
「Clickhouse系列」分布式表&本地表详解
一个逻辑上的表, 可以理解为数据库中的视图, 一般查询都查询分布式表. 分布式表引擎会将我们的查询请求路由本地表进行查询, 然后进行汇总最终返回给用户.
王知无-import_bigdata
2022/04/13
8.4K0
「Clickhouse系列」分布式表&本地表详解
clickhouse集群,双实例多副本
4台机器,clickhouse集群搭建了双副本双实例,查询统计多次出现不同数据结果,具体如下 :
用户9066404
2023/09/06
7352
分布式 PostgreSQL 集群(Citus),分布式表中的分布列选择最佳实践
在 Citus 集群上运行高效查询要求数据在机器之间正确分布。这因应用程序类型及其查询模式而异。
为少
2022/03/31
4.8K0
分布式 PostgreSQL 集群(Citus),分布式表中的分布列选择最佳实践
故障分析 | ClickHouse 集群分布式 DDL 被阻塞案例一则
现居珠海,先后担任专职 Oracle 和 MySQL DBA,现在主要负责 MySQL、mongoDB 和 Redis 维护工作。
爱可生开源社区
2022/05/23
1.3K0
故障分析 | ClickHouse 集群分布式 DDL 被阻塞案例一则
ClickHouse的副本以及分布式表的创建(九)
副本的目的主要是保障数据的高可用性,即使一台 ClickHouse 节点宕机,那么也可以从 其他服务器获得相同的数据。
Java技术债务
2022/08/09
1.3K0
Citus 分布式 PostgreSQL 集群 - SQL Reference(SQL支持和变通方案)
由于 Citus 通过扩展 PostgreSQL 提供分布式功能,因此它与 PostgreSQL 结构兼容。这意味着用户可以使用丰富且可扩展的 PostgreSQL 生态系统附带的工具和功能来处理使用 Citus 创建的分布式表。
为少
2022/05/16
1.3K0
Citus 分布式 PostgreSQL 集群 - SQL Reference(SQL支持和变通方案)
Citus 分布式 PostgreSQL 集群 - SQL Reference(手动查询传播)
当用户发出查询时,Citus coordinator 将其划分为更小的查询片段,其中每个查询片段可以在工作分片上独立运行。这允许 Citus 将每个查询分布在集群中。
为少
2022/05/16
8660
Citus 分布式 PostgreSQL 集群 - SQL Reference(手动查询传播)
Citus 分布式 PostgreSQL 集群 - SQL Reference(摄取、修改数据 DML)
要将数据插入分布式表,您可以使用标准 PostgreSQL INSERT 命令。例如,我们从 Github 存档数据集中随机选择两行。
为少
2022/03/31
1.9K0
Citus 分布式 PostgreSQL 集群 - SQL Reference(摄取、修改数据 DML)
推荐阅读
相关推荐
【ClickHouse 极简教程】分布式下的 IN/JOIN 及 GLOBAL关键字
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档