SQL解析是一项复杂的技术,一般都是由数据库厂商来掌握,由于这几年MySQL数据库中间件的兴起,需要支持读写分离、分库分表等功能,就必须从SQL中抽出表名、库名以及相关字段的值。因此像Java语言编写的Druid,Go语言编写的Kingshard等,都会对SQL进行部分解析
我们部门最近做的星云平台,内部有一个功能就是根据各种复杂的配置然后解析成可执行 SQL,这套解析框架是内部自己开发的,在配置相当复杂的时候生成的 SQL 也是非常的复杂,这个就可能出现 SQL 性能比较低下的问题,由于是异步全自动的取数过程,那么就考虑引入 SQL 解析优化引擎来内部动态优化生成的可执行 SQL
SQL解析与优化是属于编译器范畴,和C等其他语言的解析没有本质的区别。其中分为,词法分析、语法和语义分析、优化、执行代码生成。对应到MySQL的部分,如下图
词法分析
SQL解析由词法分析和语法/语义分析两个部分组成。词法分析主要是把输入转化成一个个Token。其中Token中包含Keyword(也称symbol)和非Keyword。例如,SQL语句 select username from userinfo,在分析之后,会得到4个Token,其中有2个Keyword,分别为select和from:
关键字 | 非关键字 | 关键字 | 非关键字 |
---|---|---|---|
select | username | from | userinfo |
语法分析
语法分析就是生成语法树的过程。这是整个解析过程中最精华,最复杂的部分,不过这部分MySQL使用了Bison来完成。即使如此,如何设计合适的数据结构以及相关算法,去存储和遍历所有的信息,也是值得在这里研究的。
sql 语句:
select username from user where age > 20 and level > 5;
语法分析树:
上面的语法分析是 mysql 内部的 lexer 通过 c 语言完成的,其实也有很多语法编译框架用 java 写的语法解析框架,Druid 就是其中一个
Druid SQL Parser分三个模块:
- Parser
- AST
- Visitor
Parser
parser是将输入文本转换为ast(抽象语法树),parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。
AST
AST是Abstract Syntax Tree的缩写,也就是抽象语法树。AST是parser输出的结果。这也是语法树的精髓了,sql解析,本质上就是把sql转为 ast语法树,拿到这个语法树后,我们就能做很多事了,遍历也好,加点,修改也好,都可以在ast上完成。
那么 ast 到底是什么东西?怎么去理解这个抽象语法树呢?
案例:4/(1+1)*2 这样一个数学公式
通过多个condition,以及左右叶子节点,就能构建足够复杂的语法树。那么为什么需要这么构建呢,当我们语法很简单时,那么无所谓,直接匹配字符串,或者正则也行,但是当语法足够复杂时,简单的匹配字符串,正则就不够用了,你很难检测语法是否正确,而且想要在此基础上增加语法,也几乎很难做到。而ast则把这些字符串变成结构化的数据了,你可以精确地知道一段代码里面有哪些变量名,函数名,组合条件,参数等,你可以非常精准地处理,相对于字符串处理来说,遍历数据大大降低的处理难度。而ast也常常用在如IDE中错误提示、自动补全、编译器、语法翻译、重构、代码混淆压缩转换等。
visitor
Visitor是遍历AST的手段,是处理AST最方便的模式,Visitor是一个接口,有缺省什么都没做的实现VistorAdapter。
我们可以实现不同的Visitor来满足不同的需求,Druid内置提供了如下Visitor:
- OutputVisitor用来把AST输出为字符串
- WallVisitor 来分析SQL语意来防御SQL注入攻击
- ParameterizedOutputVisitor用来合并未参数化的SQL进行统计
- EvalVisitor 用来对SQL表达式求值
- ExportParameterVisitor用来提取SQL中的变量参数
- SchemaStatVisitor 用来统计SQL中使用的表、字段、过滤条件、排序表达式、分组表达式
SQL解析使用场景
整个 SQL 解析的精髓就是理解 AST 树,那么我们拿到解析后的 SQL 可以做哪些事情呢?业界目前常用的是慢 SQL 优化、SQL 特征分析
优化分析案例(where 条件如下):
- a)1=1 and (m > 3 and n > 4)
- b)1=2 and (m > 3 and n > 4)
- c)1=1 or (m > 3 and n > 4)
特征分析案例:
select username from user where age > 20 and level > 5;
-- sql 的特征
select username from user where age > ? and level > ?;
业界著名的慢查询分析工具pt-query-digest,通过正则表达式实现这个功能但是这类处理办法Bug较多。接下来就介绍如何使用SQL解析,完成SQL特征的生成。
原始SQL | pt-query-digest生成的特征 | SQL解析器生成的特征 |
---|---|---|
select * from email_template2 where id = 1 | select * from mail_template? where id = ? | select * from email_template2 where id = ? |
REPLACE INTO a VALUES(‘INSERT INTO foo VALUES (1),(2)’) | replace into a values(\‘insert into foo values(?+) | replace into a values (?) |