开源数据库OpenGauss的SQL解析源码分析
开源数据库 OpenGauss 的 SQL 解析源码分析
OpenGauss 数据库体系概述
openGauss 是关系型数据库,采用客户端/服务器,单进程多线程架构;支持单机和一主多备部署方式,同时支持备机可读、双机高可用等特性。
从代码结构体系结构的角度来说,oepnGauss 的第一个组成部分是通信管理。
openGauss 查询响应是使用“单个用户对应一个服务器线程”的简单客户端/服务器模型实现的。由于我们无法预先知道需要建立多少连接,所以必须使用主进程(GaussMaster)来监听指定 TCP/IP(传输控制协议/网际协议)端口上的传入连接,只要连接请求 检测到,主进程将生成一个新的服务器线程。服务器线程使用信号量和共享内存相互通信,以确保整个并发数据访问期间的数据完整性。
除开通信管理之外,OpenGauss 的一大组成部分就是 SQL 引擎,承担着查询解析、查询分流、查询重写、查询优化和查询执行等任务,之后剩下的就是存储引擎了。
SQL 组成
SQL 模块简介
SQL 引擎作为数据库系统的入口,主要承担了对 SQL 语言进行解析、优化、生成执行计划的作用。对于用户输入的 SQL 语句,SQL 引擎会对语句进行语法/语义上的分析以判断是否满足语法规则等,之后会对语句进行优化以便生成最优的执行计划给执行器执行。故 SQL 引擎在数据库系统中承担着“接收信息,下达命令”的作用,是数据库系统的“脊柱神经”。
SQL 引擎负责对用户输入的 SQL 语言进行编译,在编译的过程中需要对输入的 SQL 语言进行词法分析、语法分析、语义分析,从而生成逻辑执行计划,逻辑执行计划经过代数优化和代价优化之后,产生物理执行计划,然后将执行计划交给执行引擎进行执行。
就 SQL 引擎的组成而言,SQL 引擎可以分为两部分。
SQL 解析(查询解析)
SQL 解析对输入的 SQL 语句进行词法分析、语法分析、语义分析,获得查询解析树或者逻辑计划。SQL 查询语句解析的解析器(parser)阶段包括如下:
a. 词法分析:从查询语句中识别出系统支持的关键字、标识符、操作符、终结符等,每个词确定自己固有的词性。
b. 语法分析:根据 SQL 语言的标准定义语法规则,使用词法分析中产生的词去匹配语法规则,如果一个 SQL 语句能够匹配一个语法规则,则生成对应的语法树(Abstract Synatax Tree,AST)。
c. 语义分析:对语法树(AST)进行检查与分析,检查 AST 中对应的表、列、函数、表达式是否有对应的元数据(指数据库中定义有关数据特征的数据,用来检索数据库信息)描述,基于分析结果对语法树进行扩充,输出查询树。主要检查的内容包括: ① 检查关系的使用:FROM 子句中出现的关系必须是该查询对应模式中的关系或视图。 ② 检查与解析属性的使用:在 SELECT 句中或者 WHERE 子句中出现的各个属性必须是 FROM 子句中某个关系或视图的属性。 ③ 检查数据类型:所有属性的数据类型必须是匹配的。
openGauss 中参照 SQL 语言标准实现了大部分 SQL 的主要语法功能,并结合应用过程中的具体实践对 SQL 语言进行了扩展。
查询优化
优化器(optimizer)的任务是创建最佳执行计划。一个给定的 SQL 查询(以及一个查询树)实际上可以以多种不同的方式执行,每种方式都会产生相同的结果集。如果在计算上可行,则查询优化器将检查这些可能的执行计划中的每一个,最终选择预期运行速度最快的执行计划。
优化主要分成了逻辑优化和物理优化两个部分,从关系代数和物理执行两个角度对 SQL 进行优化,进而结合自底向上的动态规划方法和基于随机搜索的遗传算法对物理路径进行搜索,从而获得较好的执行计划。
SQL 解析源码解读
SQL 解析主流程
代码文件
src/common/backend/parser/scan.l 定义词法结构,采用Lex编译后生成scan.cpp文件
src/common/backend/parser/gram.y 定义语法结构,采用Yacc编译后生成gram.cpp文件
src/common/backend/parser/scansup.cpp 提供词法分析的常用函数
src/common/backend/parser/parser.cpp 词法、语法分析的主入口文件,入口函数是raw_parser
src/common/backend/parser/analyze.cpp 语义分析的主入口文件,入口函数是parse_analyze
词法分析
parser.cpp openGauss 采用 flex 和 bison 两个工具来完成词法分析和语法分析的主要工作。对于用户输入的每个 SQL 语句,它首先交由 flex 工具进行词法分析。flex 工具通过对已经定义好的词法文件进行编译,生成词法分析的代码。
scan.l(词法分析) openGauss 中的词法文件是 scan.l,它根据 SQL 语言标准对 SQL 语言中的关键字、标识符、操作符、常量、终结符进行了定义和识别。代码如下:
定义数值类型
定义操作符
其中的 operator 即为操作符的定义,从代码中可以看出,operator 是由多个 op_chars 组成的,而 op_chars 则是[~!@#^&|`?+-*/%<>=]中的任意一个符号。但这样的定义还不能满足 SQL 的词法分析的需要,因为并非多个 op_chars 的组合就能形成一个合法的操作符,因此在 scan.l 中会对操作符进行更明确的定义(或者说检查)。
operator
词法分析其实就是将一个 SQL 划分成多个不同的 token,每个 token 会有自己的词性,在 scan.l 中定义了如下词性。
词法分析词性说明
语法分析
gram.y
在 openGauss 中,定义了一系列表达 Statement 的结构体,这些结构体通常以 Stmt 作为命名后缀,用来保存语法分析结果。
以 SELECT 查询为例,它对应了一个 Statement 结构体,这个结构体可以看作一个多叉树,每个叶子节点都表达了 SELECT 查询语句中的一个语法结构,对应到 gram.y 中,它会有一个 SelectStmt,代码如图所示。
SelectStmt
simple_select 除了上面的基本形式,还可以表示为其他形式,如 VALUES 子句、关系表达式、多个 SELECT 语句的集合操作等,这些形式会进一步的递归处理,最终转换为基本的 simple_select 形式。代码如图所示。
递归集合
在成功匹配 simple_select 语法结构后,将会创建一个 Statement 结构体,将各个子句进行相应的赋值。对 simple_select 而言,目标属性、FROM 子句、WHERE 子句是最重要的组成部分。
以目标属性为例分析,对应语法定义中的 target_list,由若干个 target_el 组成。target_el 可以定义为表达式、取别名的表达式和“*”等。当成功匹配到一个 target_el 后,会创建一个 ResTarget 结构体,用于存储目标对象的全部信息。代码如图所示。
ResTarget
parser.y
simple_select 的其他子句,如 distinctClause、groupClause、havingClause 等,语法分析方式类似。而其他 SQL 命令,如 CREATE、INSERT、UPDATE、DELETE 等,处理方式与 SELECT 命令类似。
对于任何复杂的 SQL 语句,都可以拆解为多个基本的 SQL 命令执行。在完成词法分析和语法分析后,raw_parser 函数会将所有的语法分析树封装为一个 List 结构,名为 raw_parse_tree_list,返回给 exec_simple_query 函数,用于后面的语义分析、查询重写等步骤,该 List 中的每个 ListCell 包含一个语法树。
语义分析
在完成词法分析和语法分析后,parse_analyze 函数会根据语法树的类型调用 transformSelectStmt 将 parseTree 改写为查询树。在重写过程中,parse_analyze 不仅会检查 SQL 命令是否满足语义要求,还会根据语法树对象获取更利于执行的信息,如表的 OID、列数、 等等。在某一示实例中,查询树对应的内存组织结构如图所示。目标属性、FROM 子句和 WHERE 子句的语义分析结果将分别存储在结构 TargetEntry、RangeTblEntry、FromExpr 中。
查询树内存组织结构图
在完成语义分析后,SQL 解析过程也就完成,SQL 引擎开始执行查询优化。
analyze.cpp(语义分析)
语义分析模块在词法分析和语法分析之后执行,用于检查 SQL 命令是否符合语义规定,能否正确执行。负责语义分析的是 parse_analyze 函数,位于 analyze.cpp 下。parse_analyze 会根据词法分析和语法分析得到的语法树,生成一个 ParseState 结构体用于记录语义分析的状态,再调用 transformStmt 函数,根据不同的命令类型进行相应的处理,最后生成查询树。
ParseState 结构体
ParseState 保存了许多语义分析的中间信息,如原始 SQL 命令、范围表、连接表达式、原始 WINDOW 子句、FOR UPDATE/FOR SHARE 子句等。
ParseState 结构体在语义分析入口函数 parse_analyze 下被初始化,在 transformStmt 函数下根据不同的 Stmt 存储不同的中间信息,完成语义分析后再被释放。ParseState 结构图所示。
ParseState
ParseTree 语法树——Node 结构
在语义分析过程中,语法树 parseTree 使用 Node 节点进行包装。Node 结构只有一个类型为 NodeTag 枚举变量的字段,用于识别不同的处理情况。如图所示。
Node 结构体(nodes.h)
以 SelectStmt 为例, 其对应的 NodeTag 值为 T_SelectStmt。
transformStmt 函数会根据 NodeTag 的值,将语法树转化为不同的 Stmt 结构体,调用对应的语义分析函数进行处理。
openGauss 在语义分析阶段处理的 NodeTag 情况有九种,参照着不同地 NodeTag 情况去调用 transformStmt 函数,继而完成语义分析工作。
References & Thanks
https://blog.csdn.net/GaussDB/article/details/116132257;
https://blog.csdn.net/GaussDB/article/details/119594313;
https://blog.csdn.net/GaussDB/article/details/119668883.
开源数据库OpenGauss的SQL解析源码分析的更多相关文章
- 开源网站流量统计系统Piwik源码分析——参数统计(一)
Piwik现已改名为Matomo,这是一套国外著名的开源网站统计系统,类似于百度统计.Google Analytics等系统.最大的区别就是可以看到其中的源码,这正合我意.因为我一直对统计的系统很好奇 ...
- 第八篇:Spark SQL Catalyst源码分析之UDF
/** Spark SQL源码分析系列文章*/ 在SQL的世界里,除了官方提供的常用的处理函数之外,一般都会提供可扩展的对外自定义函数接口,这已经成为一种事实的标准. 在前面Spark SQL源码分析 ...
- 第六篇:Spark SQL Catalyst源码分析之Physical Plan
/** Spark SQL源码分析系列文章*/ 前面几篇文章主要介绍的是spark sql包里的的spark sql执行流程,以及Catalyst包内的SqlParser,Analyzer和Optim ...
- 第三篇:Spark SQL Catalyst源码分析之Analyzer
/** Spark SQL源码分析系列文章*/ 前面几篇文章讲解了Spark SQL的核心执行流程和Spark SQL的Catalyst框架的Sql Parser是怎样接受用户输入sql,经过解析生成 ...
- 第二篇:Spark SQL Catalyst源码分析之SqlParser
/** Spark SQL源码分析系列文章*/ Spark SQL的核心执行流程我们已经分析完毕,可以参见Spark SQL核心执行流程,下面我们来分析执行流程中各个核心组件的工作职责. 本文先从入口 ...
- dubbo注册服务IP解析异常及IP解析源码分析
在使用dubbo注册服务时会遇到IP解析错误导致无法正常访问. 比如: 本机设置的IP为172.16.11.111, 但实际解析出来的是180.20.174.11 这样就导致这个Service永远也无 ...
- 第五篇:Spark SQL Catalyst源码分析之Optimizer
/** Spark SQL源码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer 以及核心类库TreeNode,本文将详细讲解 ...
- 第四篇:Spark SQL Catalyst源码分析之TreeNode Library
/** Spark SQL源码分析系列文章*/ 前几篇文章介绍了Spark SQL的Catalyst的核心运行流程.SqlParser,和Analyzer,本来打算直接写Optimizer的,但是发现 ...
- 开源网站流量统计系统Piwik源码分析——后台处理(二)
在第一篇文章中,重点介绍了脚本需要搜集的数据,而本篇主要介绍的是服务器端如何处理客户端发送过来的请求和参数. 一.设备信息检测 通过分析User-Agent请求首部(如下图红线框出的部分),可以得到相 ...
- MongoRepository动态代理及jpa方法解析源码分析
public interface FzkRepository extends MongoRepository<Fzk, String> { Fzk findByName(String na ...
随机推荐
- 【Azure 应用服务】更便捷的方式抓取Azure App Service for Windows的网络包
问题描述 在之前的一篇博文中,介绍了在App Service中抓取网络日志: 抓取Windows的网络包:[应用服务 App Service]App Service中抓取网络日志 抓取Linux的网络 ...
- C#多线程(7):手动线程通知
目录 区别与示例 ManualResetEvent 类 ManualResetEventSlim 区别与示例 AutoResetEvent 和 ManualResetEvent 十分相似.两者之间的区 ...
- python执行JavaScript代码出现编码问题的解决方案
当我们安装好nodejs环境,想在python代码中去调用JavaScript代码,常常会出现编码的问题. 举个例子: python代码如下: 点击查看代码 import execjs f = ope ...
- FreeRTOS教程1 基础知识
1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) Keil µVision5 IDE(MDK-Arm) 野火DAP仿真器 2.学 ...
- mybatis缓存源码解析
为什么使用缓存 减少和数据库交互次数,提高执行效率 mybatis的缓存 mybatis一级缓存,也就是局部的sqlSession级别的缓存,默认是开启的 每一个 session 会话都会有各自的缓存 ...
- springboot+kaptcha生成数学运算验证码和字符验证码
使用以下代码只需要复制粘贴,修改一处文本生成器路径即可,文中有交代. 1.添加kaptcha依赖 <dependency> <groupId>com.github.penggl ...
- nrm 安装报错 解决方案
想用 npm login 发现用的淘宝镜像,一看安装个nrm,结果运行还报错了.你看看. 改下,登录完再改回来. https://blog.csdn.net/mynewdays/article/det ...
- Vue3 好文收藏
实用!最新的几个 Vue 3 重要特性提案 http://www.zyiz.net/tech/detail-142574.html
- deepin平台安装debian的cao蛋时
我在deepin系统安装别的系统的时候,一直在boot界面无法进行下一步.困扰了我好几天,最后从电脑的左侧换成了电脑的右侧(usb)接口. 终于安装成功.你是......牛(deepin)
- Spring Boot学习日记3
学习了创建项目的几个重要核心步骤 1.创建一个新项目 2.选择spring initalizr , 可以看到默认就是去官网的快速构建工具那里实现 3.填写项目信息 4.选择初始化的组件(初学勾选 We ...