目录
记录Spark SQL生成执行计划的全流程和代码跟踪。Spark版本是2.3.2。
上图流程描述了Spark SQL 怎么转成Spark计算框架可以执行的分布式模型,下面结合一个样例,跟踪每个步骤。
0、样例说明
SQL样例:
select
t1.*,
t2.id
from
test.tb_hive_test t1
join
test.test t2
on
t1.a == t2.name and imp_date == '20221216'
test.tb_hive_test是分区表,test.test是非分区表,两张表的建表语句:
CREATE TABLE test.tb_hive_test(
a string,
b string
)
PARTITIONED BY (imp_date string COMMENT '分区时间')
Stored as ORC
CREATE TABLE test.test(
id int,
name string,
create_time timestamp
)
Stored as ORC
1、解析词义,语义,生成语法树
1.1、概念
SparK基于ANTLR语法解析SQL,ANTLR是可以根据输入自动生成语法树并可视化的显示出来的开源语法分析器。 ANTLR 主要包含词法分析器,语法分析器和树分析器。
- 词法分析器又称 Scanner、Lexer 或 Tokenizer。词法分析器的工作是分析量化那些本来毫无意义的字符流, 抽取出一些单词,例如关键字(例如SELECT)、标识符(例如STRING)、符号(例如=)和操作符(例如UNION) 供语法分析器使用。
- 语法分析器又称编译器。在分析字符流的时候,词法分析器只关注单个词语,不关注上下文。语法分析器则将收到的 Token 组织起来,并转换成为目标语言语法定义所允许的序列。
- 树分析器可以用于对语法分析生成的抽象语法树进行遍历,并能执行一些相关的操作。
1.2、Spark源码分析
当提交上述SQL是,会调用该方法:
package org.apache.spark.sql
class SparkSession private(...){
//sqlText为输入的SQL
def sql(sqlText: String): DataFrame = {
Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))
}
}
sessionState.sqlParser的parsePlan方法在AbstractSqlParser进行具体实现:
package org.apache.spark.sql.catalyst.parser
abstract class AbstractSqlParser extends ParserInterface with Logging {
//1、parsePlan函数具体实现
override def parsePlan(sqlText: String): LogicalPlan = parse(sqlText) { parser =>
astBuilder.visitSingleStatement(parser.singleStatement()) match {
case plan: LogicalPlan => plan
case _ =>
val position = Origin(None, None)
throw new ParseException(Option(sqlText), "Unsupported SQL statement", position, position)
}
}
//2、调用parse方法
protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
logDebug(s"Parsing command: $command")
//词法分析器
val lexer = new SqlBaseLexer(new UpperCaseCharStream(CharStreams.fromString(command)))
lexer.removeErrorListeners()
lexer.addErrorListener(ParseErrorListener)
val tokenStream = new CommonTokenStream(lexer)
val parser = new SqlBaseParser(tokenStream) //语法分析器
parser.addParseListener(PostProcessor)
parser.removeErrorListeners()
parser.addErrorListener(ParseErrorListener)
...
}
}
debug可以看到,执行到这里,sql被拆解成了很多类型的**Context
这些Context类都是根据ANTLR语法去解析SqlBase.g4这个DSL脚本,然后生成的,比如下面这个querySpecification就会生成QuerySpecificationContext类,里面就能通过各类参数记录下Select开头的某类SQL的词义和值。
把SQL解析成AST语法树之后,就可以开始构建逻辑执行计划。
2、Unresolved Logical Plan
回到上面得parsePlan函数,第一步生成语法树之后,第二步是使用 AstBuilder (树遍历)将语法
树转换成 LogicalPlan。这个 LogicalPlan 也被称为 Unresolved LogicalPlan。
package org.apache.spark.sql.catalyst.parser
abstract class AbstractSqlParser extends ParserInterface with Logging {
//1、parsePlan具体实现
override def parsePlan(sqlText: String): LogicalPlan = parse(sqlText) { parser =>
//2、astBuilder遍历语法树,获得Unresolved LoginPlan
astBuilder.visitSingleStatement(parser.singleStatement()) match {
case plan: LogicalPlan => plan
case _ =>
val position = Origin(None, None)
throw new ParseException(Option(sqlText), "Unsupported SQL statement", position, position)
}
}
}
从代码上看,第二步执行完成后,应该得到的是如下的执行计划,所有的表和字段,都没有关联到实际的表和元数据,只是简单的做一步SQL的解析,生成语法树。