Cobar源码分析之AST
本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。
背景
Cobar
Cobar是阿里开源的数据库中间件,关于它的介绍这里不再赘述,可以参考之前的文章《Cobar SQL审计的设计与实现》
SQL
SQL是一种领域语言(编程语言),常用于关系型数据库,方便管理结构化数据。数据库执行SQL时先对SQL进行词法分析、语法分析、语义分析生成抽象语法树(Abstract Syntax Tree,简称AST),再被优化器处理生成执行计划,由执行引擎执行。
SQL Parser
将SQL解析为AST的解析器叫SQL Parser,开发这个解析器通常有两种方式:
- 通过工具自动生成
- 优点:简单易于实现
- 缺点:性能不佳,二次开发困难
- 手工编写
- 优点:性能好,代码清晰易于扩展
- 缺点:对开发人员要求高,需要了解编译原理
Cobar中也实现了SQL Parser,它在Cobar中的位置可以从它的架构图中看到

SQL Parser之后是SQL Router,可以推断出SQL Parser解析出AST的目的是为了分库分表的路由功能。
Cobar的SQL Parser也经历了三个版本的迭代,本质是性能考虑:
- 第一版:基于JavaCC生成SQL parser,性能较差,优化不方便
- 第二版:仿照ANTLR生成的parser结构手写,中间对象过多
- 第三版:基于LL(2)识别器手写
本文不对SQL Parser做过多的介绍,有兴趣可以参考这篇文章《比开源快30倍的自研SQL Parser设计与实践》,这篇文章我也仔细阅读了几遍,附上总结的脑图:
Cobar AST
Cobar中的SQL Parser将SQL解析为AST,为了直观感受,先举个例子:
select id,type from goods as g where type in (select type from type_config where status = 0)
经过Cobar SQL Parser后,生成了如下AST对象:

这个AST的根节点就是select语句,然后每个属性都是叶子节点,叶子节点的属性再分出叶子节点。可能有点绕,需要从代码层面感受。
AST的Node定义如下,这里只有个accept方法,是为了遍历这棵树,暂时不管,后面会说到:
public interface ASTNode {
void accept(SQLASTVisitor visitor);
}
实现这个ASTNode主要有这几个:
- SQLStatement:SQL语句,比如select、update、insert等语句,体现在上图的DMLSelectStatement
- Expression:表达式,比如and、or、比较等语句,体现在InExpression、ComparisionEqualsExpression、LiteralNumber、Identifier
- TableReference:table相关语句,体现在TableReferences、TableRefFactor
以ComparisionEqualsExpression的实现为例

其中1是比较的左右表达式,2是判断符,这里是“=”,3是计算该表达式。
evaluationInternal如何实现?其实表达式被结构化和穷举之后这个问题变得简单,比如这里只需要取左右的数值,进行是否相等的比较即可。
AST操作
有了如上对AST的了解,接下来看对AST的操作,最基本的是遍历,利用ASTNode的accept,需要实现SQLASTVisitor接口,这个SQLASTVisitor定义如下:

其实是利用了java的多态,对每种ASTNode都定义了visit方法,遍历时不同对象对应到不同方法上。
比如MySQLOutputASTVisitor可以遍历AST,将AST还原为SQL输出,只需要这样:
SQLStatement stmt = SQLParserDelegate.parse(sql);
StringBuilder s = new StringBuilder();
stmt.accept(new MySQLOutputASTVisitor(s));
System.out.println(s.toString());
这样执行会输出
SELECT id, type FROM goods AS G WHERE type IN (SELECT type FROM type_config WHERE status = 0)
SQLParserDelegate.parse(sql)解析出来为DMLSelectStatement对象,它的visit方法实现如下:
@Override
public void accept(SQLASTVisitor visitor) {
visitor.visit(this);
}
再看MySQLOutputASTVisitor的visit(DMLSelectStatement node)实现:
代码比较长,这里就不贴了,总体思路是遇到叶子节点就直接按格式存入StringBuilder中,否则继续调用相应节点的accept继续遍历,是一种深度遍历的思想。
我们可以参考MySQLOutputASTVisitor编写符合自己需求的遍历器。
AST的应用
分库分表
Cobar中利用AST可以获取table名、列名、比较的值进行分库分表,这也是Cobar最重要的功能。
SQL特征生成
除此之外,我了解的AST还可以对原始SQL生成SQL特征,比如原始SQL是这样:
select id, name, age from user as u where age >= 20
或者是
select id, name, age from user as u where age >= 30
都可以被归一化为
select id, name, age from user as u where age >= ?
在进行SQL慢查询或其他的统计、针对SQL进行限流时非常有用。
危险SQL拦截
线上写了一条没有where条件的update或delete,这时可以利用AST进行表达式计算,对没有where条件和where条件恒为true的SQL进行拦截。
最后
本文从SQL AST的来源、结构、遍历原理、应用等方面进行介绍,相信看完文章会对SQL AST有了初步的了解,如果想进一步了解可以参考Cobar项目中的单元测试进行实际的演示感受。
搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。
Cobar源码分析之AST的更多相关文章
- angular源码分析:angular中脏活累活承担者之$parse
我们在上一期中讲 $rootscope时,看到$rootscope是依赖$prase,其实不止是$rootscope,翻看angular的源码随便翻翻就可以发现很多地方是依赖于$parse的.而$pa ...
- PHP扩展编写、PHP扩展调试、VLD源码分析、基于嵌入式Embed SAPI实现opcode查看
catalogue . 编译PHP源码 . 扩展结构.优缺点 . 使用PHP原生扩展框架wizard ext_skel编写扩展 . 编译安装VLD . Debug调试VLD . VLD源码分析 . 嵌 ...
- TiDB show processlist命令源码分析
背景 因为丰巢自去年年底开始在推送平台上尝试了TiDB,最近又要将承接丰巢所有交易的支付平台切到TiDB上.我本人一直没有抽出时间对TiDB的源码进行学习,最近准备开始一系列的学习和分享.由于我本人没 ...
- [Vue源码分析] v-model实现原理
最近小组有个关于vue源码分析的分享会,提前准备一下… 前言:我们都知道使用v-model可以实现数据的双向绑定,及实现数据的变化驱动dom的更新,dom的更新影响数据的变化.那么v-model是怎么 ...
- Vue.js 源码分析(二十六) 高级应用 作用域插槽 详解
普通的插槽里面的数据是在父组件里定义的,而作用域插槽里的数据是在子组件定义的. 有时候作用域插槽很有用,比如使用Element-ui表格自定义模板时就用到了作用域插槽,Element-ui定义了每个单 ...
- Vue.js 源码分析(二十五) 高级应用 插槽 详解
我们定义一个组件的时候,可以在组件的某个节点内预留一个位置,当父组件调用该组件的时候可以指定该位置具体的内容,这就是插槽的用法,子组件模板可以通过slot标签(插槽)规定对应的内容放置在哪里,比如: ...
- Vue.js 源码分析(二十四) 高级应用 自定义指令详解
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令. 官网介绍的比较抽象,显得很高大上,我个人对自定义指令的理解是:当自定义指令作用在一些DOM元素或组件上 ...
- Vue.js 源码分析(二十三) 指令篇 v-show指令详解
v-show的作用是将表达式值转换为布尔值,根据该布尔值的真假来显示/隐藏切换元素,它是通过切换元素的display这个css属性值来实现的,例如: <!DOCTYPE html> < ...
- Vue.js 源码分析(二十二) 指令篇 v-model指令详解
Vue.js提供了v-model指令用于双向数据绑定,比如在输入框上使用时,输入的内容会事实映射到绑定的数据上,绑定的数据又可以显示在页面里,数据显示的过程是自动完成的. v-model本质上不过是语 ...
随机推荐
- 面试官:MySQL 有哪些锁??
大家好,我是小林. 这次,来说说 MySQL 的锁,主要是 Q&A 的形式,看起来会比较轻松. 不多 BB 了,发车! 在 MySQL 里,根据加锁的范围,可以分为全局锁.表级锁和行锁三类. ...
- Longhorn,企业级云原生容器分布式存储 - 监控(Prometheus+AlertManager+Grafana)
内容来源于官方 Longhorn 1.1.2 英文技术手册. 系列 Longhorn 是什么? Longhorn 企业级云原生容器分布式存储解决方案设计架构和概念 Longhorn 企业级云原生容器分 ...
- Groovy+Spock单元测试
一.导入依赖 Spock是基于JUnit的单测框架,提供一些更好的语法,结合Groovy语言,可以写出更为简洁的单测. <!-- groovy依赖 --> <dependency&g ...
- kubebuilder实战之三:基础知识速览
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- CSS3图片倒影技术
http://bbs.itheima.com/thread-330315-1-1.html?wymlxt
- C#比较两个对象是否为同一个对象。 Visual Studio调试器指南---多线程应用程序调试(一)
两个对象是否为同一个对象:是看两个对象是否指向堆中的同一块内存. 1.使用object.ReferenceEquals() class Program { static void Main(strin ...
- .NET 元数据概述
元数据是一种二进制信息,用以对存储在公共语言运行库可移植可执行文件 (PE) 文件或存储在内存中的程序进行描述.将您的代码编译为 PE 文件时,便会将元数据插入到该文件的一部分中,而将代码转换为 Mi ...
- Linux的wget命令详解
[转载] Linux wget是一个下载文件的工具,它用在命令行下.对于Linux用户是必不可少的工具,尤其对于网络管理员,经常要下载一些软件或从远程服务器恢复备份到本地服务器.如果我们使用虚拟主机, ...
- Hibernate之关联关系
时间:2017-1-20 16:28 --一对多配置1.第一步:创建实体类 * 客户实体 * 订单实体 示例代码: /** * 客户实体 ...
- 在多数据源中对部分数据表使用shardingsphere进行分库分表
背景 近期在项目中需要使用多数据源,其中有一些表的数据量比较大,需要对其进行分库分表:而其他数据表数据量比较正常,单表就可以. 项目中可能使用其他组的数据源数据,因此需要多数据源支持. 经过调研多数据 ...