您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
SQL抽象语法树及改写场景应用
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
SQL抽象语法树及改写场景应用
自猿其说Tech
2022-09-30
IP归属:未知
24680浏览
数据库
### 1 背景 我们平时会写各种各样或简单或复杂的sql语句,提交后就会得到我们想要的结果集。比如sql语句,"select * from t_user where user_id > 10;",意在从表t_user中筛选出user_id大于10的所有记录。你有没有想过从一条sql到一个结果集,这中间经历了多少坎坷呢? ### 2 SQL引擎 从MySQL、Oracle、TiDB、CK,到Hive、HBase、Spark,从关系型数据库到大数据计算引擎,他们大都可以借助SQL引擎,实现“接受一条sql语句然后返回查询结果”的功能。 他们核心的执行逻辑都是一样的,大致可以通过下面的流程来概括: ![](//img1.jcloudcs.com/developer.jdcloud.com/94bc4041-25d8-4630-957a-41624f94b48020220930104414.png) 中间蓝色部分则代表了SQL引擎的基本工作流程,其中的词法分析和语法分析,则可以引申出“抽象语法树”的概念。 ### 3 抽象语法树 #### 3.1 概念 高级语言的解析过程都依赖于解析树(Parse Tree),抽象语法树(AST,Abstract Syntax Tree)是忽略了一些解析树包含的一些语法信息,剥离掉一些不重要的细节,它是源代码语法结构的一种抽象表示。以树状的形式表现编程语言的结构,树的每个节点ASTNode都表示源码中的一个结构;AST在不同语言中都有各自的实现。 ![](//img1.jcloudcs.com/developer.jdcloud.com/2b51bdc9-80ad-496a-ada6-5747bcd8607720220930104437.png) 解析的实现过程这里不去深入剖析,重点在于当SQL提交给SQL引擎后,首先会经过词法分析进行“分词”操作,然后利用语法解析器进行语法分析并形成AST。 下图对应的SQL则是“select username,ismale from userInfo where age>20 and level>5 and 1=1”; ![](//img1.jcloudcs.com/developer.jdcloud.com/b00e2f93-b79a-4b86-bfd0-115e9ad5676120220930105231.png) 这棵抽象语法树其实就简单的可以理解为逻辑执行计划了,它会经过查询优化器利用一些规则进行逻辑计划的优化,得到一棵优化后的逻辑计划树,我们所熟知的“谓词下推”、“剪枝”等操作其实就是在这个过程中实现的。得到逻辑计划后,会进一步转换成能够真正进行执行的物理计划,例如怎么扫描数据,怎么聚合各个节点的数据等。最后就是按照物理计划来一步一步的执行了。 ### 3.2 ANTLR4 解析(词法和语法)这一步,很多SQL引擎采用的是ANTLR4工具实现的。ANTLR4采用的是构建G4文件,里面通过正则表达式、特定语法结构,来描述目标语法,进而在使用时,依赖语法字典一样的结构,将SQL进行拆解、封装,进而提取需要的内容。下图是一个描述SQL结构的G4文件。 ![](//img1.jcloudcs.com/developer.jdcloud.com/d0b9ea3a-8a69-4a10-9991-4326690cf8ac20220930105337.png) #### 3.3 示例 ##### 3.2.1 SQL解析 在java中的实现一次SQL解析,获取AST并从中提取出表名。 首先引入依赖: ```xml <dependency> <groupId>org.antlr</groupId> <artifactId>antlr4-runtime</artifactId> <version>4.7</version> </dependency> ``` 在IDEA中安装ANTLR4插件; 示例1,解析SQL表名。 使用插件将描述MySQL语法的G4文件,转换为java类(G4文件忽略)。 类的结构如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/57617995-f0bb-43cf-ba4d-f5d539925a6020220930105438.png) 其中SqlBase是G4文件名转换而来,SqlBaseLexer的作用是词法解析,SqlBaseParser是语法解析,由它生成AST对象。HelloVisitor和HelloListener:进行抽象语法树的遍历,一般都会提供这两种模式,Visitor访问者模式和Listener监听器模式。如果想自己定义遍历的逻辑,可以继承这两个接口,实现对应的方法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/cb107415-3af5-49f2-87a8-6764b549740520220930105453.png) 读取表名过程,是重写SqlBaseBaseVisitor的几个关键方法,其中TableIdentifierContext是表定义的内容; ![](//img1.jcloudcs.com/developer.jdcloud.com/bb952bea-9648-4301-ab2d-e61efc7bc32c20220930105513.png) SqlBaseParser下还有SQL其他“词语”的定义,对应的就是G4文件中的各类描述。比如TableIdentifierContext对应的是G4中TableIdentifier的描述。 ##### 3.2.2 字符串解析 上面的SQL解析过程比较复杂,以一个简单字符串的解析为例,了解一下ANTLR4的逻辑。 1)定义一个字符串的语法:Hello.g4 ![](//img1.jcloudcs.com/developer.jdcloud.com/c29d552d-039e-4ec4-bbd4-9616e0483d4720220930105608.png) 2)使用IDEA插件,将G4文件解析为java类 ![](//img1.jcloudcs.com/developer.jdcloud.com/b808df35-f348-4314-93dc-31dff4b518dc20220930105623.png) 3)语法解析类HelloParser,内容就是我们定义的h和world两个语法规则,里面详细转义了G4文件的内容。 ![](//img1.jcloudcs.com/developer.jdcloud.com/105de2ed-5e1e-4e07-9c41-803c8194a28220220930105641.png) 4)HelloBaseVisitor是采用访问者模式,开放出来的接口,需要自行实现,可以获取xxxParser中的规则信息。 ![](//img1.jcloudcs.com/developer.jdcloud.com/14137505-977d-4007-b7e6-ae55d004975920220930105701.png) 5)编写测试类,使用解析器,识别字符串“hi abc”: ![](//img1.jcloudcs.com/developer.jdcloud.com/cdac1eb6-c708-4a22-8587-d8d7801557c420220930105715.png) 6)调试后发现命中规则h,解析为Hi和abc两部分。 ![](//img1.jcloudcs.com/developer.jdcloud.com/2f9ffc31-c6cd-425f-8fec-2891729eb2bd20220930105739.png) 7)如果是SQL的解析,则会一层层的获取到SQL中的各类关键key。 ### 4 SqlParser 利用ANTLR4进行语法解析,是比较底层的实现,因为Antlr4的结果,只是简单的文法解析,如果要进行更加深入的处理,就需要对Antlr4的结果进行更进一步的处理,以更符合我们的使用习惯。 利用ANTLR4去生成并解析AST的过程,相当于我们在写rpc框架前,先去实现一个netty。因此在工业生产中,会直接采用已有工具来实现解析。 Java生态中较为流行的SQL Parser有以下几种(此处摘自网络): - fdb-sql-parser 是FoundationDB在被Apple收购前开源的SQL Parser,目前已无人维护。 - jsqlparser 是基于JavaCC的开源SQL Parser,是General SQL Parser的Java实现版本。 - Apache calcite 是一款开源的动态数据管理框架,它具备SQL解析、SQL校验、查询优化、SQL生成以及数据连接查询等功能,常用于为大数据工具提供SQL能力,例如Hive、Flink等。calcite对标准SQL支持良好,但是对传统的关系型数据方言支持度较差。 - alibaba druid 是阿里巴巴开源的一款JDBC数据库连接池,但其为监控而生的理念让其天然具有了SQL Parser的能力。其自带的Wall Filer、StatFiler等都是基于SQL Parser解析的AST。并且支持多种数据库方言。 Apache Sharding Sphere(原当当Sharding-JDBC,在1.5.x版本后自行实现)、Mycat都是国内目前大量使用的开源数据库中间件,这两者都使用了alibaba druid的SQL Parser模块,并且Mycat还开源了他们在选型时的对比分析Mycat路由新解析器选型分析与结果. #### 4.1 应用场景 当我们拿到AST后,可以做什么? - 语法审核:根据内置规则,对SQL进行审核、合法性判断。 - 查询优化:根据where条件、聚合条件、多表Join关系,给出索引优化建议。 - 改写SQL:对AST的节点进行增减。 - 生成SQL特征:参考JIRA的慢SQL工单中,生成的指纹(不一定是AST方式,但AST可以实现)。 #### 4.2 改写SQL 提到改写SQL,可能第一个思路就是在SQL中添加占位符,再进行替换;再或者利用正则匹配关键字,这种方式局限性比较大,而且从安全角度不可取。 基于AST改写SQL,是用SQL字符串生成AST,再对AST的节点进行调整;通过遍历Tree,拿到目标节点,增加或修改节点的子节点,再将AST转换为SQL字符串,完成改写。这是在满足SQL语法的前提下实现的安全改写。 以Druid的SQL Parser模块为例,利用其中的SQLUtils类,实现SQL改写。 **4.2.1 新增改写** ![](//img1.jcloudcs.com/developer.jdcloud.com/128818fa-7b6d-4147-9bd4-d2d747309aa720220930105920.png) 1)原始SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/72445808-e631-4683-a092-36ea05eab83620220930105942.png) 2)实际执行SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/2a25dd75-3925-48da-bc28-e42f79730ea520220930105959.png) ##### 4.2.2 查询改写 前面省略了Tree的遍历过程,需要识别诸如join、sub-query等语法 ![](//img1.jcloudcs.com/developer.jdcloud.com/62fefc81-62a5-4143-aff1-64f53d799f3420220930110023.png) 1)简单join查询 - 原始SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/14ad3288-47e7-479d-ada6-2efa5527691d20220930110113.png) - 实际执行SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/956e12e3-770b-4aaa-90ba-f9d6d9fe8a8f20220930110132.png) 2)join查询+隐式where条件 - 原始SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/5ac1ed52-871e-46f7-9335-0ebc248f0f4320220930110152.png) - 实际执行SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/14268799-d15a-4a09-a554-71376dcc32ea20220930110210.png) 3)union查询+join查询+子查询+显示where条件 - 原始SQL (unionQuality_Union_Join_SubQuery_ExplicitCondition) ![](//img1.jcloudcs.com/developer.jdcloud.com/e1140b49-6060-4c4f-a8af-c47d4376c9e820220930110545.png) - 实际执行SQL ![](//img1.jcloudcs.com/developer.jdcloud.com/1db91497-a8b0-49c8-b1a8-eaa3472c77c320220930110607.png) ### 5 总结 本文是基于环境隔离的技术预研过程产生的,其中改写SQL的实现,是数据库在数据隔离上的一种尝试。 可以让开发人员无感知的情况下,以插件形式,在SQL提交到MySQL前实现动态改写,只需要在数据表上增加字段、标识环境差异,后续CRUD的SQL都会自动增加标识字段(flag='预发'、flag='生产'),所操作的数据只能是当前应用所在环境的数据。 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:耿宏宇
原创文章,需联系作者,授权转载
上一篇:金融科技技术演进之scyllaDB
下一篇:LUA脚本实现控单规则
相关文章
【技术干货】企业级扫描平台EOS关于JS扫描落地与实践!
突破容量极限:TiDB 的海量数据“无感扩容”秘籍
京东智联云MySQL数据库如何保障数据的可靠性?
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149964
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号