Cobar源码分析之AST

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
全局流量管理 GTM,标准版 1个月
简介: Cobar是阿里开源的数据库中间件,关于它的介绍这里不再赘述,可以参考之前的文章《Cobar SQL审计的设计与实现》Cobar中利用AST可以获取table名、列名、比较的值进行分库分表,这也是Cobar最重要的功能线上写了一条没有where条件的update或delete,这时可以利用AST进行表达式计算,对没有where条件和where条件恒为true的SQL进行拦截。

背景


Cobar


Cobar是阿里开源的数据库中间件,关于它的介绍这里不再赘述,可以参考之前的文章《Cobar SQL审计的设计与实现》


SQL


SQL是一种领域语言(编程语言),常用于关系型数据库,方便管理结构化数据。数据库执行SQL时先对SQL进行词法分析、语法分析、语义分析生成抽象语法树(Abstract Syntax Tree,简称AST),再被优化器处理生成执行计划,由执行引擎执行。


SQL Parser


将SQL解析为AST的解析器叫SQL Parser,开发这个解析器通常有两种方式:


  • 通过工具自动生成
  • 优点:简单易于实现
  • 缺点:性能不佳,二次开发困难
  • 手工编写
  • 优点:性能好,代码清晰易于扩展
  • 缺点:对开发人员要求高,需要了解编译原理

Cobar中也实现了SQL Parser,它在Cobar中的位置可以从它的架构图中看到

微信截图_20220423134634.png

SQL Parser之后是SQL Router,可以推断出SQL Parser解析出AST的目的是为了分库分表的路由功能。

Cobar的SQL Parser也经历了三个版本的迭代,本质是性能考虑:


  1. 第一版:基于JavaCC生成SQL parser,性能较差,优化不方便
  2. 第二版:仿照ANTLR生成的parser结构手写,中间对象过多
  3. 第三版:基于LL(2)识别器手写


本文不对SQL Parser做过多的介绍,有兴趣可以参考这篇文章《比开源快30倍的自研SQL Parser设计与实践》,这篇文章我也仔细阅读了几遍,附上总结的脑图:

https://github.com/lkxiaolou/reading/tree/main/xmind


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对象:

微信截图_20220423134856.png

这个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的实现为例

微信截图_20220423135114.png

其中1是比较的左右表达式,2是判断符,这里是“=”,3是计算该表达式。

evaluationInternal如何实现?其实表达式被结构化和穷举之后这个问题变得简单,比如这里只需要取左右的数值,进行是否相等的比较即可。


AST操作


有了如上对AST的了解,接下来看对AST的操作,最基本的是遍历,利用ASTNode的accept,需要实现SQLASTVisitor接口,这个SQLASTVisitor定义如下:

微信截图_20220423135213.png

其实是利用了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项目中的单元测试进行实际的演示感受。


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5月前
|
NoSQL 关系型数据库 MySQL
分布式锁的原理解析与实现工具介绍
分布式锁的原理解析与实现工具介绍
72 1
|
16天前
|
消息中间件 安全 中间件
【下饭菜】简述中间件
【下饭菜】简述中间件
36 1
|
5月前
|
存储 SQL 关系型数据库
Apache Doris 聚合函数源码阅读与解析|源码解读系列
Apache Doris Active Contributor 隐形通过本文记录下对源码的理解,以方便新人快速上手源码开发。
Apache Doris 聚合函数源码阅读与解析|源码解读系列
|
SQL 算法 Java
分库分表(4)——ShardingJDBC原理和源码分析
分库分表(4)——ShardingJDBC原理和源码分析
405 1
分库分表(4)——ShardingJDBC原理和源码分析
|
11月前
|
XML Java 数据格式
Spring源码系列:核心概念解析
Spring框架中有许多关键组件,理解这些组件的作用和关系可以帮助我们更好地阅读和理解Spring源码。BeanDefinition是Spring中重要的概念,定义了一个Bean的基本属性和行为,是Spring容器管理Bean的基础。我们可以通过注解或编程方式定义BeanDefinition,然后将其注册到Spring容器中。BeanDefinitionReader是读取和操作BeanDefinition的重要组件。其中XmlBeanDefinitionReader可以从XML文件中读取BeanDefinition,AnnotatedBeanDefinitionReader可以解析注解并注册B
|
监控 中间件 关系型数据库
MyCAT、ShardingSphere和Mocc这三个中间件的优缺点对比
MyCAT、ShardingSphere和Mocc这三个中间件的优缺点对比
|
存储 中间件 PHP
Laravel 中间件实现原理
Laravel 中间件实现原理
128 3
Laravel 中间件实现原理
|
存储 缓存 负载均衡
MyCat是干什么的?具体如何使用?底层原理是什么?
MyCat是干什么的?具体如何使用?底层原理是什么?
332 0
|
数据库
Calcite源码阅读三
利用calcite和avatica创建自己的csv服务器数据库
549 0
|
Java 关系型数据库 MySQL
Calcite源码阅读一
Calcite子项目avatica源码阅读
1306 0
Calcite源码阅读一