简易分布式计算系统设计 | 青训营笔记

142 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第21天

记录一下关于项目中SQL解析与验证部分的学习笔记

SQL解析与验证

image

  • parser

Parser是将输入文本转换为AST(抽象语法树),parser有包括两个部分,Parser和Lexer,其中Lexer实现词法分析,Parser实现语法分析。

  • AST

AST是abstract syntax tree的缩写,也就是抽象语法树。和所有的Parser一样,Druid Parser会生成一个抽象语法树。

  • Lexer&Yacc

Lexer:Lex会生成一个叫做『词法分析器』的程序。这是一个函数,它带有一个字符流传入参数,词法分析器函数看到一组字符就会去匹配一个关键字(key),采取相应措施。

{
#include <stdio.h>
}
stop printf("Stop command received\n");
start printf("Start command received\n");

编译需执行以下命令:

lex example1.l
cc lex.yy –o example –ll

Yacc:用来为编译器解析输入数据,即程序代码。还可以解析输入流中的标识符(token)。

==这两个组件共同构成了 Parser 模块,调用 Parser,可以将文本解析成结构化数据,也就是AST(抽象语法树)==

session.go 699:     return s.parser.Parse(sql, charset, collation)
session.go 699:     return s.parser.Parse(sql, charset, collation)

在解析过程中,会先用 lexer 不断地将文本转换成 token,交付给 Parser,Parser 是根据 yacc 语法 生成,根据语法不断的决定 Lexer 中发来的 token 序列可以匹配哪条语法规则,最终输出结构化的节点。 例如对于这样一条语句 SELECT * FROM t WHERE c > 1;,可以匹配 SelectStmt 的规则 ,被转换成下面这样一个数据结构:

 type SelectStmt struct {
        dmlNode
        resultSetNode
    
        // SelectStmtOpts wraps around select hints and switches.
        *SelectStmtOpts
        // Distinct represents whether the select has distinct option.
        Distinct bool
        // From is the from clause of the query.
        From *TableRefsClause
        // Where is the where clause in select statement.
        Where ExprNode
        // Fields is the select expression list.
        Fields *FieldList
        // GroupBy is the group by expression list.
        GroupBy *GroupByClause
        // Having is the having condition.
        Having *HavingClause
        // OrderBy is the ordering expression list.
        OrderBy *OrderByClause
        // Limit is the limit clause.
        Limit *Limit
        // LockTp is the lock type
        LockTp SelectLockType
        // TableHints represents the level Optimizer Hint
        TableHints []*TableOptimizerHint
    }

大部分 ast 包中的数据结构,都实现了 ast.Node接口,这个接口有一个 Accept方法,后续对 AST 的处理,主要依赖 Accept 方法,以 Visitor 模式 遍历所有的节点以及对 AST 做结构转换。

  • 制定查询计划及其优化

得到AST之后,就可以对其进行各种验证、变化、以及优化,可通过如下语句进行操作:

session.go 805:             stmt, err := compiler.Compile(goCtx, stmtNode)

进入Compile函数后,有三个重要的步骤:

  1. plan.Preprocess: 做一些合法性检查以及名字绑定;
  2. plan.Optimize:制定查询计划,并优化,这个是最核心的步骤之一,后面的文章会重点介绍;
  3. 构造 executor.ExecStmt结构:这个 ExecStmt 结构持有查询计划,是后续执行的基础,非常重要,特别是 Exec 这个方法。
  • 生成执行器
  1. 首先我们要提取出执行器的接口,定义出执行方法、事务获取和相应提交、回滚、关闭的定义,同时由于执行器是一种标准的执行过程,所以可以由抽象类进行实现,对过程内容进行模板模式的过程包装。在包装过程中定义抽象类,由具体的子类来实现。
  2. 之后是对 SQL 的处理,在执行 SQL 的时候,分为了简单处理和预处理,预处理中包括准备语句、参数化传递、执行查询,以及最后的结果封装和返回。 image

具体代码:

executor/adpter.go 227:  e, err := a.buildExecutor(ctx)

生成执行器之后,封装在一个 recordSet结构中 

return &recordSet{
            executor:    e,
            stmt:        a,
            processinfo: pi,
            txnStartTS:  ctx.Txn().StartTS(),
        }, nil
  • 运行执行器

TiDB 的执行引擎是以 Volcano 模型运行,所有的物理 Executor 构成一个树状结构,每一层通过调用下一层的 Next/NextChunk() 方法获取结果。

执行器树

这里的 rs即为一个 RecordSet接口,对其不断的调用 Next(),拿到更多结果,返回给 MySQL Client。 第二类语句是 Insert 这种不需要返回数据的语句,只需要把语句执行完成即可。这类语句也是通过 Next驱动执行,驱动点在 构造 recordSet结构之前 

// If the executor doesn't return any result to the client, we execute it without delay.
        if e.Schema().Len() == 0 {
            return a.handleNoDelayExecutor(goCtx, e, ctx, pi)
        } else if proj, ok := e.(*ProjectionExec); ok && proj.calculateNoDelay {
            // Currently this is only for the "DO" statement. Take "DO 1, @a=2;" as an example:
            // the Projection has two expressions and two columns in the schema, but we should
            // not return the result of the two expressions.
            return a.handleNoDelayExecutor(goCtx, e, ctx, pi)
        }
  • 用Druid SQL Parser解析SQL

Druid SQL Parser分三个模块:Parser,AST,Visitor。

在Druid Parser中可以通过如下方式生成AST:

final String dbType = JdbcConstants.MYSQL; // 可以是ORACLE、POSTGRESQL、SQLSERVER、ODPS等
String sql = "select * from t";
// SQLStatement就是AST
List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);

在使用过程中,需加入依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
    <scope>test</scope>
</dependency>