前沿
SQL于关系型数据库而言,重要性不言而喻。就像一个乐团的指挥,指导着作品的正确演绎和节奏的和谐统一。华为云GaussDB作为新一代关系型分布式数据库,具备卓越的技术性能和行业竞争力。很多人对GaussDB的关键技术很好奇:
GaussDB SQL语句到底是如何执行的?
GaussDB SQL引擎原理是什么?
GaussDB SQL引擎有哪些关键技术点?
…….
今天我们就从GaussDB SQL引擎入手,了解一下GaussDB SQL查询语句的执行过程,包括GaussDB SQL引擎原理和关键技术点。
首先,简单介绍一下GaussDB的系统结构,再解析GaussDB SQL查询语句的执行过程。
GaussDB系统架构
GaussDB在华为云上有集中式(图1)和分布式(图2)两种部署形态,如下图所示:
图(1) 集中式
图(2) 分布式
GaussDB SQL语句执行过程中,会涉及以下几个关键角色
GTM
全局事务管理器(Global Transaction Manager),负责生成和维护全局事务ID、事务快照、时间戳、sequence信息等全局唯一的信息。
CN
协调节点(Coordinator Node)。负责接收来自应用的访问请求,并向客户端返回执行结果;负责分解任务,并调度任务分片在各DN上并行执行。每个CN与每个DN相连接,每个CN各持有一份元数据,元数据内容相同。
DN
数据节点(Data Node)。负责存储业务数据(支持行存、列存、混合存储)、执行数据查询任务以及向CN返回执行结果。
其中,DN主要承担GaussDB SQL语句的执行,其逻辑架构如下图所示:
图(3) GaussDB逻辑架构
GaussDB两个主要引擎
SQL引擎 (SQL Engine) 和存储引擎 (Storage Engine) 。SQL引擎有时也被称为查询处理器,它的主要功能是SQL 解析、查询优化和查询执行。SQL解析对输入的 SQL 语句进行词法解析、语法解析、语义解析,从而生成查询树。查询树经过规则优化(RBO)和代价优化(CBO)之后,产生执行计划。执行器基于该执行计划对相关数据进行提取、运算、更新、删除等操作,以达到用户查询想要实现的结果。
存储引擎负责管理所有数据的I/O,它包含数据读写处理(处理行、索引、页、分配和行版本的I/O请求)、数据缓冲管理(Buffer Pool)和事务管理器。其中,事务管理又涉及到保持ACID属性的锁机制(Lock)和事务日志管理(XLOG)。
在SQL引擎和存储引擎之间是AM(Access Method) 层。AM对存储层做了封装,以支持多种存储引擎(Astore,Ustore,etc.)。SQL层不直接调用存储层,而是通过AM层,AM层会根据不同的存储引擎调用不同的执行体。
从GaussDB逻辑架构图中可以看出,GaussDB架构设计遵循现代软件系统抽象化和分层解耦的设计原则,包括:统一事务机制、统一日志系统、统一并发控制系统、统一元信息系统、统一缓存管理系统。因此,GaussDB技术架构具备以下主要特点:
SQL查询语句的执行过程
一条SQL查询(SELECT)语句的执行过程,如下所示:
从上图可以看出,一条SQL语句需要经过SQL解析生成查询树、查询优化生成执行计划,然后将执行计划转交给查询执行器做物理算子的执行操作。
SQL是介于关系演算和关系代数之间的一种描述性语言,它吸取了关系代数中一部分逻辑算子的描述,而放弃了关系代数中 “过程化” 的部分。SQL解析主要的作用就是将一个SQL语句编译成为一个由关系算子组成的查询树,通常包含词法解析、语法解析、语义解析几个子模块。
规则优化(RBO)则是在查询树的基础上进行等价的关系代数变换,把一条SQL语句转换为更高效的等价SQL,并在数据库优化器中扮演关键角色。尤其在复杂查询中,它能够在性能上带来数量级的提升。
查询执行是根据执行计划来执行SQL查询语句。底层存储方式的选择合理性,将影响查询执行效率。
解析器
GaussDB SQL解析通常包含词法解析、语法解析、语义解析:
1.词法解析:从查询语句中识别出系统支持的关键字、标识符、运算符、终结符等,确定每个词固有的词性。
SQL标准定义了SQL的关键字以及语法规则信息,GaussDB在做词法分析过程中会将一个SQL语句根据关键字信息以及间隔信息划分为独立的原子单位,每个单位以一个词的方式展现。
-
语法解析:根据定义的SQL语法规则,使用词法分析中产生的词去匹配语法规则,并生成对应的抽象语法树(Abstract Syntax Tree,AST)。
-
语义解析:对语法树进行有效性检查,检查语法树中对应的表、列、函数、表达式是否有对应的元数据,将抽象语法树转换为查询树。
语义解析的过程也是有效性语义绑定的过程,通过语义分析的检查,抽象语法树就转换成一个查询树。查询树可以通过关系代数表达式的形式来表现。
优化器
优化器是提升查询效率非常重要的一个手段,它包括规则优化和查询优化两部分。
规则优化(RBO)
规则优化是在查询树的基础上进行等价的关系代数变换,由于它是建立在关系代数基础之上的优化形式,也可称为代数优化。虽然两个关系代数式获得的结果完全相同,但是它们的执行代价却可能有很大的差异,这就构成了规则优化的基础。
规则优化遵循两个基本原则:
(1)等价性:原语句和重写后的语句输出结果相同。
(2)高效性:重写后的语句比原语句执行时间短,且资源使用更高效。
GaussDB实现了一些关键的规则优化技术,例如:
谓词下推:更早地触发条件过滤,减少处理行数
冗余运算消除:消除冗余的表、列,减少计算量
子查询提升:提升后可以匹配更多连接顺序
Outer-To-Inner转换:Inner Join可以匹配更多连接顺序
子链接提升:减少subplan和Broadcast运算
消除不等值连接:减少NestLoop和Broadcast运算
在服务大量的客户过程中,GaussDB对业务 SQL使用模式进行抽象,并实现了一些高级重写规则。在今后的栏目中,我们会对GaussDB的规则优化技术做详细介绍。
查询优化
查询优化是根据“规则优化”的输出,结合数据库内部统计信息来规划SQL语句具体的执行方式,也就是执行计划。基于优化方法的不同,查询优化技术可以分为:
(1)CBO(Cost Based Optimization,基于代价的查询优化):对SQL语句对应的待选执行路径进行代价估算,从待选路径中选择代价最低的执行路径作为最终的执行计划。
(2)ABO(AI Based Optimization,基于机器学习的查询优化):通过对历史经验的不断学习,ABO将目标场景的模式进行抽象化,形成动态的模型,自适应地针对用户的实际场景进行优化,从而获得最优的执行计划。
GaussDB采用基于CBO的优化技术,并结合ABO在建模效率、估算准确率和自适应性等方向进行积极探索,步骤如下:
图(5) 查询优化步骤
统计信息模型:统计信息是计算计划路径代价的基石,统计信息的准确度对代价估算模型中行数估算和代价估算起着至关重要的作用,直接影响查询计划的优劣。GaussDB基表数据的特征包括distinct值、MCV (Most Common Values) 值、直方图等。
行数估算 (Row Estimation):当一个约束条件确定了选择率之后,就可以确定每个计划路径所需要处理的行数,并根据行数推算出所需要处理的页面数,为代价估算做准备。
代价估算 (Cost Estimation):根据数据量估算不同算子执行代价,各算子代价之和即为计划的总代价。
当计划路径处理页面的时候,会产生I/O代价,而当计划路径处理元组的时候(例如,针对元组做表达式计算 ),会产生CPU代价。由于GaussDB是分布式数据库,在CN和DN之间传输数据又会产生通信代价,因此一个计划的总代价可以表示为:
总代价 = IO代价 + CPU代价 + 通信代价
GaussDB采用的是自底向上和随机搜索模式相结合的方式。搜索过程也都是一个从查询树向执行计划转变的过程,例如针对每个表可以有不同的扫描算子,而逻辑连接算子也可以转换为多种不同的物理连接算子。
查询优化可能需要花费较长时间,特别是在处理复杂查询时。计划缓存是GaussDB的一个重要功能,它可以缓存查询语句的执行计划,以便在下次执行相同查询时可以直接使用缓存中的执行计划,从而避免重复计算和优化查询性能。
【关键技术点】CBO + ABO:通过引入AI算法,改进CBO模型,赋予查询优化器能够根据数据而动态调整评估结果的能力。
【关键技术点】计划缓存:GaussDB 的计划缓存具备计划自适应选择和自动更新的能力。它可以自动为不同参数配置选择最佳的缓存计划,以保证查询性能的稳定和优化。
分布式查询优化
作为原生分布式数据库,分布式查询优化技术尤为重要。
GaussDB分布式架构充分运用每个节点的计算资源,且随着节点规模的扩大其整体性能也呈线性增长。为了实现分布式架构下性能和资源的利用最大化,GaussDB支持四种分布式计划,分别为CN 轻量化计划、FQS(Fast Query Shipping)计划、Stream计划和Remote-Query计划,如下图所示:
适用场景:语句可以直接在一个DN执行(单shard语句)。
适用场景:语句可以完全下推到多个DN上执行,且DN之间不需要数据交互。
适用场景:执行时CN和DN之间、DN和DN之间有数据交互的复杂语句。
适用场景:不满足前三种生成条件的极少数场景,性能非常差。
在分布式架构下,同一个表的数据会分布到不同的数据节点上,创建表的时候可以选择将数据在每个表上做哈希(Hash)分布或者随机分布。为了正确执行两表连接操作,有可能需要将两个表的数据重新分布,因此,GaussDB的分布式执行计划中增加了三个使数据形成特定分布方式的Stream算子。
分布式路径生成时,会考虑两表及连接条件上的数据是否处于同一个数据节点,如果不是,则会添加相应的数据分发算子。根据降低数据在DN节点间流动的原则来选择重分布的Stream算子。
正是基于Stream算子的合理运用,GaussDB在分布式架构下处理大规模数据才成为可能。针对Stream算子的优化也是GaussDB 查询优化的重要部分。
图(8) GaussDB分布式查询优化技术
【关键技术点】分布式查询优化:四种分布式执行计划、三个Stream算子。
执行器
执行器接收到的指令就是由优化器针对SQL查询语句而产生的执行计划,而执行计划是由一些执行算子(Operator)、表达式等组成,主要是对关系集合进行运算,最终输出用户想要的结果集。下面是几类常见的执行算子:
- 扫描算子(Scan Plan Node)
扫描节点负责从底层数据来源抽取数据,数据来源可能是来自文件系统,也可能来自网络。一般而言扫描节点都位于执行树的叶子节 点,作为执行的数据输入来源,典型代表SeqScan、IndexScan、 SubQueryScan 。
关键特征:输入数据、叶子节点、表达式过滤
- 控制算子(Control Plan Node)
控制算子一般不映射代数运算符,是为了执行器完成一些特殊的流程引入的算子,例如Limit、RecursiveUnion、Union。
关键特征:用于控制数据流程
- 物化算子(Materialize Plan Node)
物化算子一般指算法要求,在做算子逻辑处理的时候,要求把下层 的数据进行缓存处理,因为对于下层算子返回的数据量不可提前预 知,因此需要在算法上考虑数据无法全部放置到内存的情况,例如 Agg、Sort 。
关键特征:需要扫描所有数据之后才返回
- 连接算子(Join Plan Node) 这类算子是为了应对数据库中最常见的关联操作,根据处理算法和 数据输入源的不同分成MergeJoin, NestLoop Join, HashJoin。
关键特征:关联查询
- 其它算子
执行器的架构和技术也决定了数据库查询执行的整体运行效率。GaussDB执行引擎充分结合现代硬件技术的特征,采用了诸如向量化引擎、LLVM等多种现代软件技术,进行高效执行。
图(9) GaussDB全并行执行架构
GaussDB 在执行分布式计划过程中,也采用了多种技术来提升查询执行的性能。例如,在执行复杂查询时,会将重执行算子下推到 DN 节点执行,如 AGG 算子等。在下推算子执行时,会考虑数据本地性,尽可能在本地计算,减少数据在网络中的传输开销。
欢迎小伙伴们交流讨论~