目录
openGauss数据库SQL引擎
openGauss数据库执行器技术
一.openGauss数据库执行器概述
二.openGauss执行引擎
Ⅰ.执行流程
Ⅱ.执行算子
Ⅲ.表达式计算
三.高级个性介绍
openGauss存储技术
openGauss事务机制
openGauss数据库安全

openGauss数据库执行器技术

执行器在数据库整个体系结构中起到承上(优化器)启下(存储)的作用,本章首先介绍执行器的根本框架,而后引申介绍执行引擎中一些关键技术。通过本章的学习,读者应该对于执行器有根本的意识。

一、openGauss数据库执行器概述

从客户端发动一条SQL到后果返回给客户端整体的执行流程如图1所示,其中能够看到执行器所处的地位。

图1 客户端SQL执行流程示意图

如果把数据库看成一个组织,优化器位于组织最下面,是这个组织的首脑,是发号施令的指令下达机构,执行器位于组织的两头,服从优化器的指令,严格执行优化器给与的打算,将从存储空间中读取的数据进行加工解决最终返回给客户端。

第二章数据库设计中提到了SQL、关系代数之间的分割和转换,同时提到了关系操作符。关系的实质上是元组(表中的每行,即数据库中每条记录)的汇合,关系代数实际上是定义为汇合上的一系列操作。

执行器接管到的指令就是优化器应答SQL查问而翻译进去的关系代数运算符所组成的执行树。一颗形象化的执行树如图2所示:

图2 执行树示意图

其中每一个方块代表一个具体关系运算代数符,咱们称之为算子,同时留神到这里有两个箭头代表的流。其中蓝色的流代表数据流,能够看到数据从叶节点流到根节点;红色的流代表控制流,从根节点向下驱动。这里的驱动是指下层节点调用上层节点函数的数据传送函数,从上层节点要数据。

执行器的整体指标就是在每一个由优化器构建进去的执行树上,通过控制流驱动数据流在执行树上高效的流动,其流动的速度决定了执行器的解决效率。

二、openGauss执行引擎

上面分章节具体介绍openGauss的执行引擎。

Ⅰ.执行流程

执行器整体执行流程如图3所示:

图3 执行器整体执行流程图

在第一节中,咱们形容了执行器在整体数据库架构中所处的地位,执行引擎的执行流程十分清晰,分成3段。

(1) 初始化:

在这个阶段执行器会实现一些初始化工作,通常的做法是遍历整个执行树,依据每个算子的不同特色进行初始化执行。比方HashJoin这个算子,在这个阶段会进行hash表的初始化,次要是内存的调配。

(2) 执行阶段:

这个阶段是执行器最重要的局部,在这个阶段实现对于执行树的迭代(Pipeline)遍历,通过从磁盘读取数据,依据执行树的具体逻辑实现查问语义。

(3) 清理阶段:

因为在执行器的初始化阶段向零碎申请了资源,在这个阶段要实现对于资源的清理。比方在HashJoin初始化的时候对于Hash表内存申请的开释。

Ⅱ.执行算子

第一节提到表白一个SQL语句须要很多不同的代数运算符的组合。openGauss为了实现这些代数运算符的性能,引入了很多算子(Operator),算子是执行树的最根本的运算单元,这些算子依照不同的性能划分,有如下几种:

  1. 管制算子
    这些算子并不映射代数运算符,是为了执行器实现一些非凡的流程引入的算子,次要有以下几种算子。如表2所示:


表2 管制算子

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



表3 扫描算子

  1. 物化算子
    这类节点因为算法要求,在做算子逻辑解决的时候,要求把上层的数据进行缓存解决,因为对于上层算子返回的数据量不可提前预知,因而须要在算法上思考数据无奈全副搁置到内存的状况,如表4所示。


表4 物化算子

  1. 连贯算子
    这类算子是为了应答数据库中最常见的join操作,依据解决算法和数据输出源的不同分成以下几种,如表5所示。

表5 连贯算子

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


表6 Join算子连贯类型

上述三个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表的数据结构组织模式如图4所示。

图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)步的操作只到解决完所有的通过分区后的内表面。

Ⅲ.表达式计算

除了算子,为了代数运算符的齐备性,咱们还须要表达式计算。依据SQL语句的不同,表达式计算可能产生在每个算子上,用于进一步解决算子上的数据流,次要有以下两个性能:

§ 过滤:依据表达式的逻辑,过滤掉不合乎规定的数据。

§ 投影:依据表达式的逻辑,对数据流进行表达式变换,产生新的数据。

表达式计算的外围是对于表达式树的遍历和计算,后面说到算子也是用树来表白执行打算。树这个根底的数据结构在执行器的流程中表演了十分重要的地位。

咱们看上面这个SQL:

SQL2:select w_id from warehouse where 2*w_tax + 0.9 > 1 and w_city != ‘Beijing’;

SQL语句where条件前面的就是SQL表达式,如果以树的模式表白展示成如图5所示。

图5 SQL语句树的表达形式

表达式计算对算子上的数据流进行计算,通过遍历表达式计算树实现整体的表达式计算对算子上的数据流进行计算,通过遍历表达式计算树实现整体的表达式计算,(为了便于阐明,咱们对上述表达式树每个节点进行了编号,见节点前的数字),能够看到下面的图里有些树节点中标注的是 Const,这代表这个节点是一个定值节点,存储了一个定值,有些节点中标注的是ExpOp,这代表这个节点是一个计算节点,依据表达式的不同有不同的计算方法,有些节点标注的是Col, 代表从表中的某个列中读取数据。

上述的表达式计算的具体的流程如下:

(1) 根节点11代表一个AND操作符,AND逻辑是只有有一个子树的后果为false,则提前终止运算,否则进行下一个子树运算,上面有两个子表达式,咱们先解决节点9, 首先递归遍历到其子节点3。

(2) 节点3代表了一个乘法,其有两个子节点1,2,从节点1列中获得w_tax的值,从节点2中获得定值2,而后进行乘法运算,计算数据存储到节点3引擎的一处暂存空间

(3) 节点5代表一个加法运算,其有两个子节点3,4,因而从表达式树节点4上取定值9,表达式3的后果方才在第二步曾经计算了,咱们只须要读取进去,运算后果集存储到节点5暂存空间里。

(4) 节点9代表一个比拟运算,其有两个子节点5, 6,因而将表达式树节点5存储的数据和树节点6上的数据定值1进行大于比拟,如果后果为false,则提前终止以后的表达式运算,跳入下一行,从新从(1)开始计算,如果为true,则进行下一个子表达式的计算。

(5) 节点9曾经处理完毕,咱们接着解决节点10。

(6) 节点10代表字符串不等于比拟运算,其有两个子节点7,8,从节点7中进行获得w_city值,同时从节点8中获得定值字符串“Beijing”, 而后进行不等于字符串比拟运算,如果为true,输入tuple, 否则从新从(1)开始计算。

由此可见,通过遍历整个表达式树,依据表达式树不同节点的类型做出相应的动作,有些是对于数据的读取,有些是进行函数计算。表达式计算树叶子节点都来自数据流中的数据或者定值,而非叶子节点都是计算函数。

未完待续......下一篇文章将对“高级个性”进行介绍。