暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

openGauss执行算子

openGauss小助手 2021-10-23
389

8.1节提到表达一个SQL语句需要很多不同的代数运算符的组合。openGauss为了完成这些代数运算符的功能,引入了很多算子(Operator),算子是执行树的最基本的运算单元,这些算子按照不同的功能划分,有如下几种:

1. 控制算子

这些算子并不映射代数运算符,是为了执行器完成一些特殊的流程引入的算子,主要有以下几种算子。如表8-2所示:

表8-2 控制算子

算子类型描述
Result处理仅需要一次计算的条件表达式或insert中的value子句
Append处理大于或者等于2的子树流程
BitmapAnd需要对两个或以上位图进行并操作的流程
BitmapOr需要对两个或以上位图进行或操作的流程
RecursiveUnion用于处理with recursive递归查询
Limit用于处理下层数据的limit操作
VecToRow用于普通执行器和向量化执行器之间数据传输的转换

2. 扫描算子

扫描算子负责从底层数据来源抽取数据,数据来源可能是来自文件系统,也可能来自网络(分布式查询)。扫描节点都位于执行树的叶子节点,作为执行数的数据输入来源。如表8-3所示:

表8-3 扫描算子

算子类型描述
Seqscan 顺序扫描行存
CstoreScan顺序扫描列存
DfsScan顺序扫描HDFS类文件系统
Stream顺序扫描来自网络的数据流,数据流一般来自其他子树执行分发到网络中的数据
BitmapHeapScan通过bitmap结构获取元组
BitmapIndexScan利用索引获取满足条件的bitmap结构
TidScan通过事先得到的Tid来扫描heap上的数据
SubQueryScan从子查询的输出来扫描数据
ValueScan扫描Values子句产生的数据源
CteScan扫描cte表达式
WorkTableScan扫描RecursiveUnion产生的迭代数据
FunctionScan扫描Function产生的批量数据
IndexScan扫描索引得到Tid,然后从heap上扫描数据
IndexOnlyScan在某些情况下,可以只用扫描索引就能得到查询想要的数据,因此不需要扫描heap
ForgeinScan从用户定义的外表数据源扫描数据

3. 物化算子

这类节点因为算法要求,在做算子逻辑处理的时候,要求把下层的数据进行缓存处理,因为对于下层算子返回的数据量不可提前预知,因此需要在算法上考虑数据无法全部放置到内存的情况,如表8-4所示。

表8-4 物化算子

算子类型描述
Sort对下层数据进行排序,例如快速排序
Group对下层已经排序的数据进行分组
Agg对下层数据进行分组(无序)
Unique对下层数据进行去重操作
Hash对下层数据进行缓存,存储到一个hash表里
SetOp对下层数据进行缓存,用于处理intersect等集合操作

4. 连接算子

这类算子是为了应对数据库中最常见的join操作,根据处理算法和数据输入源的不同分成以下几种,如表8-5所示。

表8-5 连接算子

算子类型描述
Nestloop对下层两股数据流实现循环嵌套连接操作
MergeJoin对下层两股排序数据流实现归并连接操作
HashJoin对下层两股数据流实现哈希连接操作

同时为了应对不同的连接操作,openGauss定义了如下的连接类型。定义两股数据流,一股为S1(左),一股为S2(右),Join算子连接类型如表8-6所示:

表8-6 Join算子连接类型

Join算子连接类型描述
Inner Join内连接,对于S1和S2上满足条件的数据进行连接操作。
Left Join左连接,对于S1没有匹配S2的数据,进行补空输出。
Right Join右连接,对于S2没有匹配S1的数据,进行补空输出。
Full Join 全连接,除了Inner Join的输出部分,对于S1,S2没有匹配的部分,进行各自补空输出
Semi Join半连接,当S1能够在S2中找到一个匹配的,单独输出S1
Anti Join反连接,当S1能够在S2中找不到一个匹配的,单独输出S1

上述三个Join算子都已经支持上述6个不同的连接类型。

NestLoop算子:对于左表中的每一行,扫描一次右表。算法简单,但非常耗时(计算笛卡尔乘积),如果可以用索引扫描右表则这可能是一个不错的策略。可以将左表的当前行中的值用作右索引扫描的键。

MergeJoin:在join开始前,先对每个表按照连接属性(join attributes)进行排序。然后并行扫描两个表,组合匹配的行形成join行。MergeJoin只需扫描一次表。排序可以通过排序算法或使用连接键上的索引来实现。

HashJoin:先扫描内表,并根据其连接属性计算hash值作为散列键(hash key)存入散列表(hash table)中。然后扫描外表,计算hash key,在hash table中找到匹配的行。

对于Join的表无序的情况,MergeJoin需要两个表扫描并进行排序,复杂度会达到O(nlogn),而NestLoop是一种嵌套循环的查询方式,复杂度到O(n^2)。而Hashjoin借助hash表来加速查询,复杂度基本在O(n)。

不过HashJoin只适用于等值连接,对于>、<、<=、>=这样的连接还需要NestLoop这种通用的连接方式来处理。如果连接键是索引列本来就有序,或者SQL本身需要排序,那么用MergeJoin的代价会比Hashjoin更小。

下面我们简单介绍下HashJoin执行流程。

HashJoin顾名思义就是利用Hash表来进行Join查询,Hash表的数据结构组织形式如图8-4所示。

图8-4 Hash表

可以看到Hash表根据Hash值分成多个桶,相同的Hash键值的元组用链表的方式串联在一起,因为hash算法的高效和hash表的唯一指向性,hashjoin的匹配效率非常高,但是hashjoin只能支持等值查询。

Hashjoin节点有两颗子树,一颗我们称之为外表,另外一颗我们称之为内表,内表输出的数据用于生成hash表,而外表生成的数据则在hash表上进行探查并返回join结果。

在内外表的选择上,优化器一般根据这两个子树的代价进行分析选择。因为Hash表需要申请内存进行存放,因此优化器倾向于输出行数少的子树作为内表,这样数据能够被内存存放的概率比较大,如果存放不下,则需要进行下盘操作。

HashJoin主要执行流程如下面描述:

(1) 扫描内表元组,根据连接键计算hash值,并插入到hash表中的根据hash值计算出来的槽位上。这个步骤中,会反复读取内表元组直到把内表读取完全,并将hash表构建出来。

(2) 扫描外表元组,根据连接键计算hash值,直接查找hash表进行连接操作,并将结果输出,在这个步骤中,会反复读取外表直到外表读取完毕,这个时候join的结果也将全部输出。

上面说了,如果当前内表的元组无法全部放在内存里,会进行下盘操作,hashjoin对于下盘支持的设计思想非常精妙,采用了典型的分而治之算法。其主要的流程如下描述。

(3) 根据内表和外表的键值的hash值,对内表和外表进行分区,经过分区之后,内表和外表划分成很多小的内外表,这里的划分原则是相同的hash值分区之后数据要划分到相同下标的内外表中,同时内表的数据要能够存放在内存里。

(4) 取相同下标的内外表,重复(1)、(2)里面的算法进行元组输出。

(5) 重复第(4)步的操作只到处理完所有的经过分区后的内外表。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论