执行流程
大致的流程是这样的,先记好每个阶段,我们来一起探索每个阶段在哪里,又做了什么
代码入口
入口在SparkSession类,里面有一个lazy sessionState,里面很多对象都是lazy懒加载,在最后使用时程序全部执行
首先会调用.sql()
sqlParser解析sql语句
SQL语句(字符串)传递给sessionState.sqlParser,调用parsePlan()把一个sql字符串语句解析为一个抽象语法树(Spark称此阶段为Unresoved logicPlan)
这个阶段是基于ANTLR实现,期间会校验关键字和语法
Analyzer解析
然后SparkSession.sessionState.analyzer对象,走到Analyzer类
调用executeAndCheck()把Unresoved logicPlan转为 Analyzed logicPlan
这个类也有一些规则需要执行,主要在batch里面
另外这个阶段还做了几件事,特别重要的有这几个:
- 关联元数据,校验字段函数表
- 数据类型绑定及函数绑定
- 类型推断
比如dt = dateadd('2024-01-01',1)在这阶段转换为 cast(dt as date) = dateadd(cast('2024-01-01' as date),1),analyzer在最后会调用一个规则,其中就包含类型转换,TypeCoercionBase 记录了转换的规则,string优先级很低
optimizer
接着传递给sessionState.optimizer
这个阶段主要是基于RBO的规则优化,主要对sql优化,比较重要的有这几个
- 谓词下推(Predicate Pushdown) ,比如下图语法树变为先过滤后join
- 常量累加(Constant Folding) ,100+80在此步骤优化为180,不会在每条数据都执行100+80
- 列值裁剪(Column Pruning),只会扫描需要的列,优化网络传输,内存和扫描效率
SparkPlan
传递给sessionState.planner,SparkPlan类
--------------------------------
此阶段有一个 strategies 非常值得深入学习,另外顺便看下SparkPlan的继承关系,很多人都搞不清SparkPlan是什么,PhysicalPlan是什么
-----------------------回到正题
SparkPlanner的继承关系。生成物理计划的策略保存在Strategies属性下。plan()方法是生成物理计划的入口,将strategies依次应用到LogicalPlan,生成物理计划候选集合。对于PlanLater类型的 SparkPlan,其doExecute()方法没有实现,表示不支持执行,所起到的作用仅仅是占位作用。SparkPlanner会取出所有的PlanLater算子,递归调用plan()方法进行替换。之后,调用prunePlans()方法对物理计划列表进行过滤,去掉一些不够高效的物理计划 。在生成SparkPlan列表后,调用prepareForExecution()根据需要插入shuffle操作和格式转换以适应Spark执行,主要工作是检查是否有不匹配的partition类型,如果不兼容就增加一个Exchange节点,用来重新分区,这样就最终生成了execute plan
查询规划器生成的物理计划会被传递给sessionState.prepareForExecution,它会进行额外的优化,比如重新排序join操作等。
最后,经过所有处理的物理计划会被提交给SparkSession.sparkContext,在Spark集群上执行。