目标:分析一个SQL语句的执行过程。
首先,要了解一下什么是AST树。
抽象语法树(abstract syntax code,AST)是源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这所以说是抽象的,是因为抽象语法树并不会表示出真实语法出现的每一个细节,比如说,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。抽象语法树并不依赖于源语言的语法,也就是说语法分析阶段所采用的上下文无文文法,因为在写文法时,经常会对文法进行等价的转换(消除左递归,回溯,二义性等),这样会给文法分析引入一些多余的成分,对后续阶段造成不利影响,甚至会使合个阶段变得混乱。因些,很多编译器经常要独立地构造语法分析树,为前端,后端建立一个清晰的接口。
从CRDB的工作流程图来看,这些一个个planNode就是抽象的语法树节点。
打开pkg/sql/plan.go文件,找到planNode的定义:
type planNode interface {
Start(params runParams) error
Next(params runParams) (bool, error)
Values() parser.Datums
Close(ctx context.Context)
}
Start()函数的功能是初始运行当前节点,Next()函数相当于对下一行数据进行处理,得到的数据可以通过Values()进行读取,最后销毁节点时调用Close()。
这是一个接口类型,看看下面,定义了许多的全局变量,这些就是AST树上的节点,但都是空的,这里主要是做一些空节点的初始化。
再看一个接口类型,
type planMaker interface {
newPlan(ctx context.Context, stmt parser.Statement, desiredTypes []parser.Type,) (planNode, error)
makePlan(ctx context.Context, stmt Statement) (planNode, error)
prepare(ctx context.Context, stmt parser.Statement) (planNode, error)
}
通过这个planMaker就可以递归地对构建AST树了,一条复杂的SQL语句最后都是返回一个planNode的接口。
在得到根结点的planNode之后,CRDB会进行执行期优化,检测并判断当前SQL走的是单机工作流程还是分布式的工作流程。
通常,一些系统的查询就会走单机工作流程模式,直接调用startPlan()对根结点进行Start,Next,Values,Close的工作。
最后,通过一个ForEachRow()函数就可以对每行的数据进行相应的编排处理了。
而当数据分布在多节点上的时候,就会走分布式流程的结果。
比如一条简单的SELECT * FROM TABLE_NAME;
可以查看pkg/sql/executor.go文件,这里和单机的查询最大区别就是用了一个distSQLPlanner。
这个Planner能够将请求封装成runnerRequest,然后下发到节点上去处理。
而节点的具体处理逻辑在pkg/sql/distsqlrun/tablereader.go文件中进行了实现。
最后的数据就会由RowFetcher一行行地读出来。
你要是问我怎么知道是在这里找到的,请看
https://github.com/cockroachdb/cockroach/issues/27551