共计 4307 个字符,预计需要花费 11 分钟才能阅读完成。
目录
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),算子是执行树的最根本的运算单元,这些算子依照不同的性能划分,有如下几种:
- 管制算子
这些算子并不映射代数运算符,是为了执行器实现一些非凡的流程引入的算子,次要有以下几种算子。如表 2 所示:
表 2 管制算子
- 扫描算子
扫描算子负责从底层数据起源抽取数据,数据起源可能是来自文件系统,也可能来自网络(分布式查问)。扫描节点都位于执行树的叶子节点,作为执行数的数据输出起源。如表 3 所示:
表 3 扫描算子
- 物化算子
这类节点因为算法要求,在做算子逻辑解决的时候,要求把上层的数据进行缓存解决,因为对于上层算子返回的数据量不可提前预知,因而须要在算法上思考数据无奈全副搁置到内存的状况,如表 4 所示。
表 4 物化算子
- 连贯算子
这类算子是为了应答数据库中最常见的 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)开始计算。
由此可见,通过遍历整个表达式树,依据表达式树不同节点的类型做出相应的动作,有些是对于数据的读取,有些是进行函数计算。表达式计算树叶子节点都来自数据流中的数据或者定值,而非叶子节点都是计算函数。
未完待续 …… 下一篇文章将对“高级个性”进行介绍。