SQL处理流程之Parser | 青训营笔记

484 阅读5分钟

这是我参与「第四届青训营 」笔记创作活动的第2天

前言

SQL语言的入门并不困难,即使是萌新在数十个小时的学习后也能很顺利地编写出SQL查询语句,但是在实际项目尤其是大数据相关的项目中我们往往发现SQL的实际执行效果不如人意, 例如

  1. 查询性能低
  2. 执行时间过长
  3. 等待时间过长
  4. SQL写的太差(尤其体现在多表查询上)
  5. 索引失效
  6. 服务器参数(缓存,线程数)设置不合理
  7. 项目需求不合理 等等

因此,我们需要了解SQL的底层处理流程是怎样的,就会比较容易去优化。

首先我们要知道,客户端传入的SQL语句后并不能马上执行,是需要经过一系列复杂的流程,最终转变成二进制的机器码,才能被执行的,我们需要对执行的SQL进行优化,那么就有必须先来了解下,一个SQL语句的执行有哪些主要环节,以查询的SQL来举例

8d7957f50fe578d82dc054f79183151.jpg

是不是觉得比较复杂?简化一下可以转为下面这个流程图:

未命名绘图.jpg

今天我首先来介绍一下 Parser(解析器) 部分。

Parser(解析器)

Parser的本质是把字符串形式的SQL给解析成 AST(abstract syntax tree) ,也就是说SQL Parser的入参是string,结果就是一个AST。

image.png

什么是AST(抽象语法树)?

在计算机科学领域内,AST表示的是你写的编程语言源代码的抽象语法结构。
有的时候直接处理字符串会是更快速更好的解决方式,但是当源程序语法非常复杂的时候字符串处理的复杂度已经不是一个简单的事了。而AST则把这些字符串变成结构化的数据了,你可以精确地知道一段代码里面有哪些变量名,函数名,参数等,你可以非常精准地处理,相对于字符串处理来说,遍历数据大大降低的处理难度。

当SQL语句转为一个抽象语法树,其基本结构如图:

15d07abbb6e38972da2cbcbf3cccbfe.png

Parser的处理步骤

  1. 词法分析:拆分字符串,得到关键词、数值常量、字符串常量、运算符号等token

Token分类如下

  • 注释
  • 关键字(SELECT、CREATE)
  • 操作符(+、-、>=)
  • 开闭合标志((、CASE)
  • 占位符(?)
  • 空格
  • 引号包裹的文本、数字、字段
  • 方言语法(${variable})
  1. 语法分析:将token组成AST node,最终得到一个AST

解析顺序为角度,语法分析分为两种,自顶而下自底而上

自顶而下一般采用递归下降方式处理,称为 LL(k),第一个 L 是指从左到右分析,第二个 L 指从左开始推导,k 是指超前查看的数量,如果实现了回溯功能,k 就是无限大的,所以带有回溯功能的 LL(k) 几乎是最强大的。LL 系列一般分为 LL(0)、LL(1)、LL(k)、LL(∞)。

自底而上一般采用移进(shift)规约(reduce)方式处理,称为 LR,第一个 L 也是从左到右分析,第二个 R 指从右开始推导,而规约时可能产生冲突,所以通过超前查看一个符号解决冲突,就有了 SLR,后面还有功能更强的 LALR(1) LR(1) LR(k)

通过这张图可以看到 LL 家族与 LR 家族的能力范围:

image.png 如图所示,无论 LL 还是 LR 都解决不了二义性文法,还好所有计算机语言都属于无二义性文法。

SQL解析的实现方式

不同分析引擎中Parser的实现方式不同:
递归下降(ClickHouse)、Flex 和 Bison (PostgreSQL)、JavaCC(Flink)、Antlr(Spark、Presto)等。

本篇笔记暂时先介绍 Flex 和 Bison
flex和 bison 是两个用来生成程序的工具(这两个工具的用途很广泛,这里只说它在 PostgreSQL 中的使用)。在 PostgreSQL 中,它们被用来生成 SQL 语句的词法和语法分析器。注意,flex 和 bison 本身不是词法或语法分析器,利用它们生成的程序才是。即:

image.png

flex 和 bison 生成的词法和语法分析器的工作可以概述为:flex 生成的词法分析器将输入拆分成一个一个的记号(token),bison 生成的语法分析器根据已有的规则,分析这些 token 的组合,是否是符合语法规范的。
flex 源文件的扩展名为 .l,bison 源文件的扩展名为 .y。它们的格式比较类似,分为三个段:
(1)定义段,这一部分一般是一些声明及选项设置等。C 语言的注释、头文件包含等一般就放在 %{ %} 之间,这一部分的内容会被直接复制到输出文件的开头部分,选项后面再说;

(2)规则段,为一系列匹配模式和动作,模式一般使用正则表达式书写,动作部分为 C 代码:

模式1 {动作1(C 代码) }

      在输入和模式 1 匹配的时候,执行动作部分的代码;

(3)用户子程序段,这里为 C 代码,会被原样复制到输出文件中,一般这里定义一些辅助函数等,如动作代码中使用到的辅助函数。

词法分析器所做的,就是在输入中寻找字符的模式(pattern)。在词法分析器中,我们要给定我们需要识别的模式,因此需要使用一种方式来描述模式,这就是常用的正则表达式。

总结

作为SQL初学者,对于SQL Parser的了解尚浅,之后会继续学习,不断更新,期待大佬指正。