0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ClickHouse内幕(3)基于索引的查询优化

京东云 来源:jf_75140285 作者:jf_75140285 2024-06-11 10:46 次阅读

ClickHouse索引采用唯一聚簇索引的方式,即Part内数据按照order by keys有序,在整个查询计划中,如果算子能够有效利用输入数据的有序性,对算子的执行性能将有巨大的提升。本文讨论ClickHouse基于索引的查询算子优化方式。

在整个查询计划中Sort、Distinct、聚合这3个算子相比其他算子比如:过滤、projection等有如下几个特点:1.算子需要再内存中保存状态,内存代价高;2.算子计算代价高;3.算子会阻断执行pipeline,待所有数据计算完整后才会向下游输出数据。所以上算子往往是整个查询的瓶颈算子。

本文详细讨论,3个算子基于索引的查询优化前后,在计算、内存和pipeline阻断上的影响。

实验前准备:

后续的讨论主要基于实验进行。

CREATE TABLE test_in_order
(
    `a` UInt64,
    `b` UInt64,
    `c` UInt64,
    `d` UInt64
)
ENGINE = MergeTree
ORDER BY (a, b);

表中总共有3个part,每个part数据量4条。

PS: 用户可以在插入数据前提前关闭后台merge,以避免part合并成一个,如果part合并成一个将影响查询并行度,可能对实验有影响,以下查询可以关闭后台merge:system stop merges test_in_order

一、Sort算子

如果order by查询的order by字段与表的order by keys的前缀列匹配,那么可以根据数据的有序特性对Sort算子进行优化。

1.Sort算子实现方式

首先看下不能利用主键有序性的场景,即对于order by查询的order by字段与表的order by keys的前缀列不匹配。比如下面的查询:

query_1: EXPLAIN PIPELINE SELECT b FROM read_in_order ORDER BY b ASC

它的执行计划如下:

┌─explain───────────────────────────────┐
│ (Expression)                          │
│ ExpressionTransform                   │
│   (Sorting)                           │
│   MergingSortedTransform 3 → 1        │
│     MergeSortingTransform × 3         │
│       LimitsCheckingTransform × 3     │
│         PartialSortingTransform × 3   │
│           (Expression)                │
│           ExpressionTransform × 3     │
│             (ReadFromMergeTree)       │
│             MergeTreeThread × 3 0 → 1 │
└───────────────────────────────────────┘

排序算法由3个Transform组成,其中

1)PartialSortingTransform对单个Chunk进行排序;

2)MergeSortingTransform对单个stream进行排序;

3)MergingSortedTransform合并多个有序的stream进行全局sort-merge排序

wKgaomZnupqAPI15AAB2MeV7qvk592.png


如果查询的order by字段与表的order by keys的前缀列匹配,那么可以根据数据的有序特性对查询进行优化,优化开关:optimize_read_in_order。

2.匹配索引列的查询

以下查询的order by字段与表的order by keys的前缀列匹配

query_3: EXPLAIN PIPELINE SELECT b FROM test_in_order ORDER BY a ASC, b ASCSETTINGS optimize_read_in_order = 0 -- 关闭read_in_order优化

查看order by语句的pipeline执行计划

┌─explain───────────────────────────┐
│ (Expression)                      │
│ ExpressionTransform               │
│   (Sorting)                       │
│   MergingSortedTransform 3 → 1    │
│     MergeSortingTransform × 3     │
│       (Expression)                │
│       ExpressionTransform × 3     │
│         (ReadFromMergeTree)       │
│         MergeTreeThread × 3 0 → 1 │
└───────────────────────────────────┘

此时order by算子的算法

1)首先MergeSortingTransform对输入的stream进行排序

2)然后MergingSortedTransform将多个排好序的stream进行合并,并输出一个整体有序的stream,也是最终的排序结果。

这里有个疑问在关闭read_in_order优化的查询计划中,系统直接默认了MergeSortingTransform的输入在Chunk内是有序的,这里其实是一个默认优化,因为order by查询的order by字段与表的order by keys的前缀列匹配,所以数据在Chunk内部一定是有序的。

3. 开启优化optimize_read_in_order

┌─explain──────────────────────────┐
│ (Expression)                     │
│ ExpressionTransform              │
│   (Sorting)                      │
│   MergingSortedTransform 3 → 1   │
│     (Expression)                 │
│     ExpressionTransform × 3      │
│       (ReadFromMergeTree)        │
│       MergeTreeInOrder × 3 0 → 1 │
└──────────────────────────────────┘

4. 优化分析

打开optimize_read_in_order后:

1.对于计算方面:算法中只有一个MergingSortedTransform,省略了单个stream内排序的步骤

2.由于内存方面:由于MergeSortingTransform是消耗内存最大的步骤,所以优化后可以节约大量的内存

3.对于poipeline阻塞:MergeSortingTransform会阻塞整个pipeline,所以优化后也消除了对pipeline的阻塞

二、Distinct算子

如果distinct查询的distinct字段与表的order by keys的前缀列匹配,那么可以根据数据的有序特性对Distinct算子进行优化,优化开关:optimize_distinct_in_order。通过以下实验进行说明:

1. Distinct算子实现方式

查看distinct语句的pipeline执行计划

query_2: EXPLAIN PIPELINE SELECT DISTINCT * FROM woo.test_in_order SETTINGS optimize_distinct_in_order = 0 -- 关闭distinct in order优化
┌─explain─────────────────────────────┐
│ (Expression)                        │
│ ExpressionTransform                 │
│   (Distinct)                        │
│   DistinctTransform                 │
│     Resize 3 → 1                    │
│       (Distinct)                    │
│       DistinctTransform × 3         │
│         (Expression)                │
│         ExpressionTransform × 3     │
│           (ReadFromMergeTree)       │
│           MergeTreeThread × 3 0 → 1 │
└─────────────────────────────────────┘

Distinct算子采用两阶段的方式,首先第一个DistinctTransform在内部进行初步distinct,其并行度为3,可以简单的认为有3个线程在同时执行。然后第二个DistinctTransform进行final distinct。

每个DistinctTransform的计算方式为:首先构建一个HashSet数据结构,然后根据HashSet,构建一个Filter Mask(如果当前key存在于HashSet中,则过滤掉),最后过滤掉不需要的数据。

2.开启优化optimize_distinct_in_order

┌─explain────────────────────────────────┐
│ (Expression)                           │
│ ExpressionTransform                    │
│   (Distinct)                           │
│   DistinctTransform                    │
│     Resize 3 → 1                       │
│       (Distinct)                       │
│       DistinctSortedChunkTransform × 3 │
│         (Expression)                   │
│         ExpressionTransform × 3        │
│           (ReadFromMergeTree)          │
│           MergeTreeThread × 3 0 → 1    │
└────────────────────────────────────────┘

可以看到初步distinct和final distinct采用了不同的transform,DistinctSortedChunkTransform和DistinctTransform。

DistinctSortedChunkTransform:对单个stream内的数据进行distinct操作,因为distinct列跟表的order by keys的前缀列匹配,scan算子读取数据的时候一个stream只从一个part内读取数据,那么每个distinct transform输入的数据就是有序的。所以distinct算法有:

DistinctSortedChunkTransform算法一:

Transform中保留最后一个输入的数据作为状态,对于每个输入的新数据如果跟保留的状态相同,那么忽略,如果不同则将上一个状态输出给上一个算子,然后保留当前的数据最为状态。这种算法对于在整个stream内部全局去重时间和空间复杂度都有极大的降低。

wKgaomZnup2AV9P5AAAkb6cOov0046.png


DistinctSortedStreamTransform算法二:(ClickHouse采用的)

Transform对与每个Chunk(ClickHouse中Transform数据处理的基本单位,默认大约6.5w行),首先将相同的数据划分成多个Range,并设置一个mask数组,然后将相同的数据删除掉,最后返回删除重复数据的Chunk。

wKgZomZnup2AVsteAAA1RbKTsnk642.png


3. 优化分析

打开optimize_distinct_in_order后:主要对于第一阶段的distinct步骤进行了优化,从基于HashSet过滤的算法到基于连续相同值的算法。

1.对于计算方面:优化后的算法,省去了Hash计算,但多了判断相等的步骤,在不同数据基数集大小下,各有优劣。

2.由于内存方面:优化后的算法,不需要存储HashSet

3.对于poipeline阻塞:优化前后都不会阻塞pipeline

三、聚合算子

如果group by查询的order by字段与表的order by keys的前缀列匹配,那么可以根据数据的有序特性对聚合算子进行优化,优化开关:optimize_aggregation_in_order。

1.聚合算子实现方式

查看group by语句的pipeline执行计划:

query_4: EXPLAIN PIPELINE SELECT a FROM test_in_order GROUP BY a SETTINGS optimize_aggregation_in_order = 0 -- 关闭read_in_order优化
┌─explain─────────────────────────────┐
│ (Expression)                        │
│ ExpressionTransform × 8             │
│   (Aggregating)                     │
│   Resize 3 → 8                      │
│     AggregatingTransform × 3        │
│       StrictResize 3 → 3            │
│         (Expression)                │
│         ExpressionTransform × 3     │
│           (ReadFromMergeTree)       │
│           MergeTreeThread × 3 0 → 1 │
└─────────────────────────────────────┘

对于聚合算子的整体算法没有在执行计划中完整显示出来,其宏观上采用两阶段的聚合算法,其完整算法如下:1.AggregatingTransform进行初步聚合,这一步可以并行计算;2.ConvertingAggregatedToChunksTransform进行第二阶段聚合。(PS:为简化起见,忽略two level HashMap,和spill to disk的介绍)。

2.开启优化optimize_aggregation_in_order

执行计划如下:

┌─explain───────────────────────────────────────┐
│ (Expression)                                  │
│ ExpressionTransform × 8                       │
│   (Aggregating)                               │
│   MergingAggregatedBucketTransform × 8        │
│     Resize 1 → 8                              │
│       FinishAggregatingInOrderTransform 3 → 1 │
│         AggregatingInOrderTransform × 3       │
│           (Expression)                        │
│           ExpressionTransform × 3             │
│             (ReadFromMergeTree)               │
│             MergeTreeInOrder × 3 0 → 1        │
└───────────────────────────────────────────────┘

可以看到打开optimize_aggregation_in_order后aggregating算法由三个步骤组成:

1)首先AggregatingInOrderTransform会将stream内连续的相同的key进行预聚合,预聚合后在当前stream内相同keys的数据只会有一条;

2)FinishAggregatingInOrderTransform将接收到的多个stream内的数据进行重新分组使得输出的chunk间数据是有序的,假设前一个chunk中group by keys最大的一条数据是5,当前即将输出的chunk中没有大于5的数据;

3)MergingAggregatedBucketTransform的作用是进行最终的merge aggregating。

wKgaomZnup2ARICmAABfrfxtQaI394.png


FinishAggregatingInOrderTransform的分组算法如下:

假设有3个stream当前算子会维护3个Chunk,每一次选取在当前的3个Chunk内找到最后一条数据的最小值,比如初始状态最小值是5,然后将3个Chunk内所有小于5的数据一次性取走,如此反复如果一个Chunk被取光,需要从改stream内拉取新的Chunk。

wKgZomZnup6AEeZ2AABVTVDACO0969.png


这种算法保证了每次FinishAggregatingInOrderTransform向下游输出的Chunk的最大值小于下一次Chunk的最小值,便于后续步骤的优化。

3.优化分析

打开optimize_aggregation_in_order后:主要对于第一阶段的聚合步骤进行了优化,从基于HashMap的算法到基于连续相同值的算法。

1.对于计算方面:优化后的算法,减少了Hash计算,但多了判断相等的步骤,在不同数据基数集大小下,各有优劣。

2.由于内存方面:优化前后无差别

3.对于poipeline阻塞:优化前后无差别

四、优化小结

在整个查询计划中Sort、Distinct、聚合这3个算子算子往往是整个查询的瓶颈算子,所以值得对其进行深度优化。ClickHouse通过利用算子输入数据的有序性,优化算子的算法或者选择不同的算法,在计算、内存和pipeline阻塞三个方面均有不同程度的优化。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Pipeline
    +关注

    关注

    0

    文章

    28

    浏览量

    9301
  • 算子
    +关注

    关注

    0

    文章

    16

    浏览量

    7242
收藏 人收藏

    评论

    相关推荐

    优化嵌入式DSP软件的编译器

    确定哪个索引或一组索引对于优化很重要取决于应用程序开发人员的目标。例如,性能优化意味着开发人员可以使用速度较慢或成本较低的 DSP 来完成相同数量的工作。
    发表于 05-03 09:45 81次阅读
    <b class='flag-5'>优化</b>嵌入式DSP软件的编译器

    MySQL联表查询优化

    条数据,本意想查出
    的头像 发表于 04-24 12:33 297次阅读
    MySQL联表<b class='flag-5'>查询</b><b class='flag-5'>优化</b>

    Redis官方搜索引擎来了,性能炸裂!

    RediSearch 是一个 Redis 模块,为 Redis 提供查询、二级索引和全文搜索功能。
    的头像 发表于 02-21 10:01 1082次阅读
    Redis官方搜<b class='flag-5'>索引</b>擎来了,性能炸裂!

    谷歌搜索引优化的各个方面和步骤

    谷歌搜索引擎是最受欢迎和广泛使用的搜索引擎之一,为了使你的网站在谷歌上更好地排名并提高曝光度,你可以采取一些谷歌搜索引优化的步骤。 使用关键字研究工具,如Google AdWords
    的头像 发表于 01-25 10:29 478次阅读

    导致MySQL索引失效的情况以及相应的解决方法

    导致MySQL索引失效的情况以及相应的解决方法  MySQL索引的目的是提高查询效率,但有些情况下索引可能会失效,导致查询变慢或效果不如预期
    的头像 发表于 12-28 10:01 494次阅读

    Mysql索引是什么东西?索引有哪些特性?索引是如何工作的?

    作为开发人员,碰到了执行时间较长的 sql 时,基本上大家都会说” 加个索引吧”。但是索引是什么东西,索引有哪些特性,下面和大家简单讨论一下。
    的头像 发表于 12-24 16:20 572次阅读
    Mysql<b class='flag-5'>索引</b>是什么东西?<b class='flag-5'>索引</b>有哪些特性?<b class='flag-5'>索引</b>是如何工作的?

    春兴精工实控人孙洁晓涉内幕交易获缓刑三年

    公开信息显示,早在2018年1月,孙洁晓因涉嫌内幕交易被证监会立案调查。2019年3月,公司披露了证监会的行政处罚决定书,孙被禁止处理持有的股票以及罚款。同年10月,孙因涉嫌内幕交易罪被取保候审。随着这次司法判决的下发,此事终于告一段落。
    的头像 发表于 12-18 09:46 375次阅读

    Cascades查询优化器基本原理分析

    优化器一般由三个组件组成:统计信息收集、开销模型、计划列举。 如图 2 所示,开销模型使用收集到的统计信息以及构造的不同开销公式,估计某个特定查询计划的成本,帮助优化器从众多备选方案中找到开销最低的计划。
    的头像 发表于 12-15 09:38 290次阅读
    Cascades<b class='flag-5'>查询</b><b class='flag-5'>优化</b>器基本原理分析

    ClickHouse 联合创始人、前 Google 副总裁 Yury 到访杭州玖章算术公司,双方建立生态合作

    10 月 31 日,ClickHouse 联合创始人 Yury 到访未来科技城,与玖章算术创始人叶正盛和国际总经理 Ni Demai 展开沟通与推进合作。ClickHouse 是深受开发者青睐的实时
    的头像 发表于 11-17 11:23 609次阅读
    <b class='flag-5'>ClickHouse</b> 联合创始人、前 Google 副总裁 Yury 到访杭州玖章算术公司,双方建立生态合作

    索引的底层实现详解

    说一说索引的底层实现? Hash索引 基于哈希表实现,只有精确匹配索引所有列的查询才有效,对于每一行数据,存储引擎都会对所有的索引列计算一个
    的头像 发表于 10-09 10:26 566次阅读
    <b class='flag-5'>索引</b>的底层实现详解

    索引是什么意思 优缺点有哪些

    的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。而且
    的头像 发表于 10-09 10:19 1863次阅读

    MySQL索引的常用知识点

    索引结构:B+树 索引其实是一种数据结构 注意B+树是MySQL,索引默认的结构;一张表至少有一个索引(主键索引),是可以有多个
    的头像 发表于 09-30 16:43 292次阅读

    MySQL优化并不像大家所想的那样简单

    说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *、不使用NULL字段、合理创建索引、为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?
    的头像 发表于 08-23 09:40 492次阅读
    MySQL<b class='flag-5'>优化</b>并不像大家所想的那样简单

    日增320TB数据,从ClickHouse迁移至ByConity后,查询性能十分稳定!

    早期这套系统部署在ClickHouse集群,一方面,由于业务的高速发展导致数据量日益膨胀,每日最大新增数据超过320TB,每日新增行数超过2.3万亿条,用户数据维度超过2万多个;另一方面,用户查询需求更加灵活和多样化,需要同时支持明细
    的头像 发表于 08-04 15:37 418次阅读
    日增320TB数据,从<b class='flag-5'>ClickHouse</b>迁移至ByConity后,<b class='flag-5'>查询</b>性能十分稳定!

    为什么列存储能够大幅度提高数据的查询性能

    列存储索引适合于数据仓库中,主要执行大容量数据加载和只读查询,与传统面向行的存储方式相比,使用列存储索引存储可最多提高 10 倍查询性能 ,与使用非压缩数据大小相比,可提供多达 7 倍
    的头像 发表于 07-09 16:11 417次阅读
    为什么列存储能够大幅度提高数据的<b class='flag-5'>查询</b>性能