一、主要流程
本期主要为大家分享,在经过语法、词法分析并生成 AST 语法树后的执行流程,下图是完整流程展示:
图 1 整体流程图
一个 Query 语句执行,从 connExecutor 接收,再到解析完成的 AST 语法树,最后执行成功返回结果,预计共要经过优化、生成逻辑计划、生成物理计划、分布式执行计划、收集结果返回 5 个步骤。
通过形象的例子来描述这项流程:“今年过节回家,回家的行程未确定”,“应该乘坐什么交通工具回到”等,这些便是我们需要执行的语句。
语句解析后,由 connExecutor 接收语句,会生成一个初步计划,比如先坐火车,再转乘飞机,下飞机之后再坐客车回到家,这个初步计划可理解为逻辑计划。
确定初步计划,还需要确定具体计划,比如需要坐 xxx 次高铁到 xxx 地,转 xxx 班次 xxx 航班的飞机到 xxx 地,坐 xxx 号客车回家,这项具体计划可相当于物理计划。后面我们按照具体计划执行,就相当于引擎执行物理计划,最后返回结果。
此篇文章主要介绍核心组件 connExecutor、逻辑计划和物理计划,为了方便大家理解,本文将通过围绕一条具体的 SQL 语句进行阐述。
图 2 SQL 例句
二、核心组件
connExecutor 是执行器的核心数据结构,在前期通过消息识别和分发来处理来自客户的不同类型 SQL 语句,下图是例句的 stmt 对象:
图 3 例句 stmt 对象
connExecutor 负责查询处理来自于所给客户端的连接请求,它使用了一个基于 PGsession 级语义的状态机,这个状态机用于对客户端的请求进行异步写操作,它从 stmtBuff 中接受输入的 statement,并通过 clientComm 接口进行结果的处理。
connExecutor 维护 stmtBuff 的游标,从而在同一时刻进行指令的执行和结果处理。这个游标总是指向正在处理的 statement。
而在游标生成前,相应 statement 的结果已经产生,游标之后的 statement 就会被放在执行队列之中准备被执行,connExecutor 还负责删除不再需要被执行的 statements。
connExecutor 具体有如下职责:
1. 向执行引擎派发查询,通过识别 stmtBuff 的协议类型并进行分类,再对不同类型,调用不同的执行方法,如图 1 中所示,对于数据库执行的语句,一般都是 execStmt 类型;
2. 执行结束后,回复客户端查询结果;
3. 维护连接的状态机,状态机分为以下两个方向:
-
维护连接的事务状态;
-
维护游标的位置。
三、逻辑计划
在数据库中,为实现执行语句的所有操作,数据库会把语句解析成一个个算子,每个算子分别执行语句,并组合成最后结果。
每个算子我们称之为 planNode,逻辑计划实际上就是语句所需的所有算子组合成的树形结构。它在语法、语义解析后,构建物理计划执行前,为构建物理计划添加一些额外的表、列信息等。
在走优化时,逻辑计划会从优化后的 memo 转换成一个 planNode 的树状结构,不走优化或者不支持优化时,逻辑计划由 AST 直接转换,从下向上构建。
例句在生成逻辑计划时,由于其为 join 语句,会先生成一个 joinNode。在 joinNode 中,算子主要是存储连接的结果,因此其下面的子算子才是真正执行的算子。
如果是嵌套 join,子算子仍可能是 joinNode,例句中 joinNode 下的子算子是两个 scanNode,scanNode 是处理扫描表的键/值对,并将它们重新构造为行的算子。例句的算子对象如图 4 所示:
图 4 算子对象
数据库大概通过几十个算子来覆盖语句的所有操作,下图列举了几个算子,此处不再一一赘述:
图 5 数据库算子
逻辑计划负责把对应的 relExpr(或者 AST)转换成对应的 planNode,包括 scanNode 中 tableDesc 的数据填充,其他 node 中 colDesc 的信息补充和 filter 过滤条件转化为 span 等过程。
创建逻辑计划前要构建 execBuilder.Build,此结构体主要包括三个部分: execFactory, memo, RelExpr。
其中 memo 和 RelExpr 是创建 memo 过程产生的。execFactory 中包括 planner,planner 贯穿 SQL 的整个执行流程,其中包括构建 memo 中原数据用到的 schema 信息、 table 信息等,最后结果存储在 planner 的 planTop 中。
图 6 builder 结构
通过数据库执行 EXPLAIN 命令查看执行语句的逻辑计划:
图 7 逻辑计划
或者在 AdminUI 语句页面可以看到执行语句的逻辑计划:
图 8 AdminUI 展示
四、物理计划
物理计划是根据逻辑计划生成的树型结构,在执行引擎实际查询时,即按照物理计划来进行的。
它根据逻辑计划中不同执行算子的节点生成,在构建物理计划时,会采用分阶段 (stage) 的方式添加相应的 processor,实际上保存在 PhysicalPlan 结构体中的是处理器链路。
图 9 物理计划类图
在数据库执行 EXPLAIN ANALYSE 命令执行查询并生成具有执行统计信息的物理查询计划,例句物理计划如图 10 所示:
图 10 物理计划展示
例句的物理计划数据对象结构如下:
图 11 物理计划数据对象
flow 根据物理计划 processors 中的 nodeID 生成 flowSpec,例句的数据对象结构如下:
图 12 Flow数据对象
物理计划生成之后,会根据 range 分布到各个 node 上执行,最终汇总结果,返回到客户端,一个 Query 流程完成。