您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
Apache顶级项目ShardingSphere — SQL Parser的设计与实现
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
Apache顶级项目ShardingSphere — SQL Parser的设计与实现
京东科技技术说
2021-01-06
IP归属:未知
179000浏览
数据库
Sql
![](//img1.jcloudcs.com/developer.jdcloud.com/5a4b49f7-ab2f-4a69-a6f8-230c57054e4b20210106103901.jpg) 导语:SQL作为现代计算机行业的数据处理事实标准,是目前最重要的数据处理接口之一,从传统的DBMS(如MySQL,Oracle)到主流的计算框架(如spark,flink)都提供了SQL的解析引擎,因此想对SQL进行精细化的操作,一定离不开SQL Parser。Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,需要对SQL进行精细化的操作,如改写,加密等,因此也实现了SQL Parser,并提供独立的Parser引擎。 先来认识一下传统数据库中一条SQL处理流程是怎样的,接受网络包 -》数据库协议解析网络包的到SQL -》SQL语法解析为抽象语法树 -》把语法树转换成关系代数表达式树(逻辑执行计划) -》再转换成物理算子树(物理执行计划) -》遍历物理算子树执行相应算子的实现获取数据并返回。 # 一、工作原理 SQL Parser 的功能是把一条SQL解析为抽象语法树(AST),SQL Parser需要编译原理相关的知识,简单介绍一下。 SQL Parser 包含词法解析(Lexer)和语法解析(Parser),词法解析的作用是把一个SQL 分割成一个一个不可分割的单元,例子: 原始SQL:select id,name from table1 where name="xxx"; 词法解析器输入是原始SQL,并且暴露一个接口nextToken(),每次调用nextToken()都会返回一个Token,伪代码如下: ```java String originSql = "select id,name from table1 where name='xxx'"; Lexer lexer = new Lexer(originSql); while(!lexer._hitEOF){// 判断是否结束 System.out.printLn(lexer.nextToken()) } // 输出如下: select id , name from table1 where name = 'xxx' ``` 语法分析器的作用是使用Lexer的输出(调用nextToken()),构造出AST,以上面的sql为例,解析器得出的语法树如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/1b61c611-4ed2-4915-b812-c78ff351962820210106104758.png) ## 1、Lexer 原理 Lexer 也称为分词,从左向右扫描SQL,将其分割成一个个的toke(不可分割的,具有独立意义的单元,类似英语中的单词)。 Lexer的实现一般都是构造DFA(确定性有限状态自动机)来实现的,以一个例子说明。 状态转移图如下,这是一个能够识别标识符,数字和一般运算符的词法解析器。 ![](//img1.jcloudcs.com/developer.jdcloud.com/a3982d58-435b-4457-98ac-869a958b980f20210106104826.png) 代码实现可以使用传统的while case的模板实现,伪代码如下: ```java Class Token{...} Enum State { BEGIN,OPERATER,IDENTIFIER,NUMBER } Class Lexer{ String input = "..."; State state = BEGIN; int index = 0; Token nextToken() { while(index < input.length) { Char char = input.charAt(index); switch (state) { case BEGIN: switch (char){ case a-zA-Z_ : index++; state = IDENTIFIER; case +-*/ : state = BEGIN return Token(OPERATER) case 0-9: state = NUMBER; index++; default: return null; } case IDENTIFIER: switch (char){ case a-zA-Z_0-9 : index++; state = IDENTIFIER; default : state = BEGIN; index--; return Token(IDENTIFIER) } case NUMBER: switch (char){ case 0-9 : index++; state = NUMBER; default : state = BEGIN; index--; return Token(NUMBER) } default: return null; } } } } ``` ## 2、Parser 原理 Parser阶段有两种类型方法来实现,一种是自顶向下分析法,另一种是自底向上分析法,简单介绍一下两种类型分析法的处理思路。 首先给出上下文无关语法和相关术语的定义: - **终结符集合 T(terminal set)** 一个有限集合,其元素称为 终结符(terminal)。 - **非终结符集合 N(non-terminal set)** 一个有限集合,与 T 无公共元素,其元素称为 非终结符(non-terminal)。 - **符号集合 V(alphabet)** T 和 N 的并集,其元素称为 符号(symbol)。因此终结符和非终结符都是符号。符号可用字母:A,B,C,a,b,c 等表示。 - **符号串(a string of symbols)** 一串符号,如 X1 X2 ... Xn 。只有终结符的符号串称为句子(sentence)。空串 (不含任何符号)也是一个符号串,用 ε 表示。符号串一般用小写字母 u, v, w 表示。 - **产生式(production)** 一个描述符号串如何转换的规则。对于上下文本无关语法,其固定形式为:A -> u ,其中 A 为非终结符, u 为一个符号串。 - **产生式集合 P(production)** 一个由有限个产生式组成的集合。 - ** 展开 (expand)** 一个动作:将一个产生式 A -> u 应用到一个含有 A 的符号串 vAw 上,用 u 代替该符号串中的 A ,得到一个新的符号串 vuw 。 - **折叠 (reduce)** 一个动作:将一个产生式 A -> u 应用到一个含有 u 的符号串 vuw 上,用 A 代替该符号串中的 u ,得到一个新的符号串 vAw。 - **起始符号 S(start symbol)** N 中的一个特定的元素。 - **推导(derivate)** 一个过程:从一个符号串 u 开始,应用一系列的产生式,展开到另一个的符号串 v,若 v 可以由 u 推导得到,则可写成:u => v。 - **上下文本无关语法 G (context-free grammar, CFG)** 一个 4 元组:(T,N,P,S) ,其中 T 为终结符集合, N 为非终结符集合, P 为产生式集合, S 为起始符号。一个句子如果能从语法 G 的 S 推导得到,可以直接称此句子由语法 G 推导得到,也可称此句子符合这个语法,或者说此句子属于 G 语言。G 语言 (G language) 就是语法 G 推导出来的所有句子的集合,有时也用 G 代表这个集合。 - **解析(parse)** 也称为分析,是一个过程:给定一个句子 s 和语法 G ,判断 s 是否属于 G ,如果是,则找出从起始符号推导得到 s 的全过程。推导过程中的任何符号串(包括起始符号和最终的句子)都称为 中间句子(working string)。 ### 2.1 自顶向下分析法 LL(1) LL(1) 是自顶向下分析法的一种,第一个L代表从左向右扫描待解析文本,第二个L代表从左向右展开,(1)代表每次读取一个Token。 以简单表达式为例子: ```java // 语法规则, 整个是一个产生式,左边expr为非终结符,NUM是一 //个Token,为终结符 expr: NUM | NUM expr // 待解析文本 1 2 23 45 ``` 解析过程如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/f946c2cb-04ea-45d0-a9e3-43b707c7fd7c20210106105819.png) 我们的目标是将起始符号expr展开成句子 1 2 23 45 1. 对比expr和NUM(1),只能选择expr -> NUM expr,才可以和NUM(1)匹配,展开后得NUM expr 2. 忽略已匹配的NUM(1), 再次读入NUM(2),只能选择expr -> NUM expr,展开后得NUM expr 3. 忽略已匹配的NUM(2),再次读入NUM(23),只能选择expr -> NUM expr,展开后得NUM expr 4. 忽略已匹配的NUM(23),再次读入NUM(45),只能选择expr -> NUM,展开后得NUM 5. 得到最终句子 NUM(1) NUM(2) NUM(23) NUM(45) 可接受,解析完成 注意点:为什么1,2,3只能选择expr -> NUM expr, Lexer中会有接口判断是否得到末尾,ShardingSphere中接口是lexer._hitEOF。 ### 2.1 自底向上分析法 LR(1) 自底向上分析的顺序和自顶向下分析的顺序相反,从给定的句子开始,不断的挑选出合适的产生式,将中间句子中的子串折叠为非终结符,最终折叠到起始符号。 LR(1) 是自底向上分析法的一种,第一个L代表从左向右扫描待解析文本,第二个R代表从右向坐折叠,(1)代表每次读取一个Token。 以简单表达式为例: ```java // 语法规则, 整个是一个产生式,左边expr为非终结符,NUM是一 //个Token,为终结符 expr: NUM | expr NUM // 待解析文本 1 2 23 45 ``` 解析过程是如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/cac5a7a0-cf3e-47e9-9450-d0c359d4b61520210106105958.png) 我们的目标是把 1 2 23 45 折叠为expr 1. 读入NUM(1),发现只能选择expr -> NUM, 折叠得expr 2. 读入NUM(2),只能选择expr -> expr NUM,折叠得expr 3. 读入NUM(23),只能选择expr -> expr NUM,折叠得expr 4. 读入NUM(45),只能选择expr -> expr NUM,折叠得expr 可接受,解析完成 # 二、ShardingSphere Parser 实现 实现Parser的方式一般分为两种,一种是写代码实现状态机来进行解析,另一种是通过解析器生成器根据定义的语法规则生成解析器,ShardingSphere使用第二种方式,这是由于衡量了性能,扩展性和容易维护因素最终决定的。 以ShardingSphere中的MySQL解析引擎为例,模块shardingsphere-sql-parser-mysql,语法定义路径src/main/antlr ![](//img1.jcloudcs.com/developer.jdcloud.com/d49438f3-c317-4edd-a14b-951f667fb52e20210106110033.png) 功能点: - 提供独立的SQL解析引擎 - 可以方便的对语法规则进行扩充和修改 - 提供SQL 变量参数化功能 - 提供SQL 格式化功能 - 支持多种方言 ![](//img1.jcloudcs.com/developer.jdcloud.com/7b34e6c1-b60c-4dba-9a88-e258cb190ac120210106110103.png) 使用方法: ```java //maven 依赖 <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-sql-parser-engine</artifactId> <version>${project.version}</version> </dependency> // 根据需要引入指定方言的解析模块(以MySQL为例),可以添加所有支持的方言,也可以只添加使用到的 <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-sql-parser-mysql</artifactId> <version>${project.version}</version> </dependency> //获取语法树 /** * databaseType type:String 可能值 MySQL,Oracle,PostgreSQL,SQL92,SQLServer * sql type:String 解析的SQL * useCache type:boolean 是否使用缓存 * @return parse tree */ ParseTree tree = new SQLParserEngine(databaseType).parse(sql, useCache); // 获取SQLStatement /** * databaseType type:String 可能值 MySQL,Oracle,PostgreSQL,SQL92,SQLServer * useCache type:boolean 是否使用缓存 * @return SQLStatement */ ParseTree tree = new SQLParserEngine(databaseType).parse(sql, useCache); SQLVisitorEngine sqlVisitorEngine = new SQLVisitorEngine(databaseType, "STATEMENT"); SQLStatement sqlStatement = sqlVisitorEngine.visit(tree); ``` # 三、常见解析器 **1. MySQL Parser** 词法解析通过手写代码的方式实现,语法解析通过定义语法规则,使用bison生成语法解析代码。 **2. PostgreSQL Parser** 通过定义语法规则,使用flex/bison生成词法,语法解析代码。 **3. TIDB Parser(能够单独使用,golang)** 词法解析通过手写代码的方式实现,语法解析通过定义语法规则,使用goyacc生成语法解析代码。 **4. ShardingSphere Parser(能够单独使用, 多种目标语言c,c++,java, golang,python)** - 通过定义语法规则,使用ANTLR生成词法,语法解析代码 - 可以方便自定义语法 - 提供缓存机制 - SQL格式化,SQL参数化 - 支持多种DB:Mysql, PostgreSQL, SQLServer, Oracle, SQL92 - 支持自定义visitor **5. alibaba druid(能够单独使用,java)** - 通过代码的方式实现词法解析器和语法解析器 - 支持多种DB: Mysql, PostgreSQL, SQLServer, Oracle, odps, db2, hive, SQL92 - SQL格式化,SQL参数化 - 支持自定义visitor **6. Jsqlparser(能够单独使用,java)** - 通过javacc定义语法解析规则实现 - 不局限于某一个DB 注意: - 基本都是通过定义语法来实现的,词法解析都是通过定义正则语言,构造有限状态自动机实现,区别是LL(自顶向下)语法和LR(自底向上)语法。 - antlr使用的是LL(自顶向下)的语法规则 改进的LL(*)算法,Bison和goyacc使用LR(自底向上)的语法规则,LALR算法,Jsqlparser是LL(k), druid目测类似LL(k)。 - LR相比LL表达能力更强,典型的例子是antlr不支持相互左递归,bison支持。 # 四、AST(语法树)应用 转化为逻辑执行计划,逻辑执行计划再转换为物理执行计划,物理执行计划用于存储引擎具体执行。 eg: select * from table1, table2 where table1.id=1 转化为逻辑执行计划为 ![](//img1.jcloudcs.com/developer.jdcloud.com/0faab29f-3cd4-4f55-adae-9ab9575ddde920210106110323.png) 通过遍历语法树,对SQL进行格式化,参数化等 #### 参考文献 [1] LL(*):https://www.antlr.org/papers/LL-star-PLDI11.pdf [2] LALR:https://suif.stanford.edu/dragonbook/lecture-notes/Stanford-CS143/11-LALR-Parsing.pdf;http://www.cs.ecu.edu/karl/5220/spr16/Notes/Bottom-up/lalr.html [3] TiDB优化器设计:https://pingcap.com/blog-cn/tidb-cascades-planner [4] MySQL优化器设计:https://dev.mysql.com/doc/refman/8.0/en/cost-model.html #### 本文作者 陆敬尚,京东数科软件工程师,Apache ShardingSphere Committer,热爱开源,现专注于ShardingSphere 的开源建设和开发工作。 #### 招聘信息 京东数科长期招聘Apache ShardingSphere的开源工程师,欢迎优秀的开源人才加入,共同打造出色的Apache顶级项目!简历投递邮箱:zhangliang@apache.org。 <br/> >原文链接:https://mp.weixin.qq.com/s/2Nkspx8SBr6qa-mOtxLnUg 了解更多数科技术最佳实践&创新成果,请关注“京东数科技术说”微信公众号 ![](//img1.jcloudcs.com/developer.jdcloud.com/3df7b1f7-8c64-43a6-b41a-6c38eb3a317320210106110718.png)
原创文章,需联系作者,授权转载
上一篇:再见,2020。你好,2021!
下一篇:Insight Mybatis 日志打印及使用建议
相关文章
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
突破容量极限:TiDB 的海量数据“无感扩容”秘籍
京东智联云MySQL数据库如何保障数据的可靠性?
京东科技技术说
文章数
17
阅读量
69031
作者其他文章
01
AAAI 2021论文:利用深度元学习对城市销量进行预测(附论文下载)
在2020年12月收录的AAAI 2021(CCF-A类)上,京东城市被收录了一篇名为《Robust Spatio-Temporal Purchase Prediction via Deep Meta Learning》的论文。该论文研究了如何通过深度元学习,结合城市中的各项信息以及历史的销量数据,对未来,特别是大型购物节期间,城市中各个区域不同时间段的销量进行预测。
01
京东支付SDK重构设计与实现
基于复杂业务流的领域驱动设计(DDD)实践
01
京东数科七层负载 | HTTPS硬件加速 (Freescale加速卡篇)
通过将耗CPU计算资源的加密算法计算卸载到Freescale加速卡的方法进行SSL/TLS性能优化。
01
Apache顶级项目ShardingSphere — SQL Parser的设计与实现
SQL Parser的工作原理、常见解析器及具体应用
最新回复
丨
点赞排行
共0条评论
京东科技技术说
文章数
17
阅读量
69031
作者其他文章
01
AAAI 2021论文:利用深度元学习对城市销量进行预测(附论文下载)
01
京东支付SDK重构设计与实现
01
京东数科七层负载 | HTTPS硬件加速 (Freescale加速卡篇)
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号