- 常量累加(Constant Folding)
常量累加就是比如计算x+(100+80)->x+180
,虽然是一个很小的改动,但是意义巨大。如果没有进行优化的话,每一条结果都需要执行一次100+80
的操作,然后再与结果相加。优化后就不需要再次执行100+80
操作。
- 列值裁剪(Column Pruning)
列值裁剪是当用到一个表时,不需要扫描它的所有列值,而是扫描只需要的id,不需要的裁剪掉。这一优化一方面大幅度减少了网络、内存数据量消耗,另一方面对于列式存储数据库来说大大提高了扫描效率。
步骤4. SparkPlanner模块:转化为物理执行计划
根据上面的步骤,逻辑执行计划已经得到了比较完善的优化,然而,逻辑执行计划依然没办法真正执行,他们只是逻辑上可行,实际上Spark并不知道如何去执行这个东西。比如join是一个抽象概念,代表两个表根据相同的id进行合并,然而具体怎么实现合并,逻辑执行计划并没有说明。
此时就需要将逻辑执行计划转化为物理执行计划,也就是将逻辑上可行的执行计划变为Spark可以真正执行的计划。比如join算子,Spark根据不同场景为该算子制定了不同的算法策略,有BroadcastHashJoin
、ShuffleHashJoin
以及SortMergejoin
等,物理执行计划实际上就是在这些具体实现中挑选一个耗时最小的算法实现,怎么挑选,下面简单说下:
- 实际上SparkPlanner对优化后的逻辑计划进行转换,是生成了多个可以执行的物理计划Physical Plan;
- 接着CBO(基于代价优化)优化策略会根据Cost Model算出每个Physical Plan的代价,并选取代价最小的 Physical Plan作为最终的Physical Plan。
以上2、3、4步骤合起来,就是Catalyst优化器!
步骤5. 执行物理计划
最后依据最优的物理执行计划,生成java字节码,将SQL转化为DAG,以RDD形式进行操作。
总结:整体执行流程图
四、Catalyst 的两大优化
这里在总结下Catalyst优化器的两个重要的优化。
1. RBO:基于规则的优化
优化的点比如:谓词下推、列裁剪、常量累加等。
- 谓词下推案例:
select * from table1 a join table2 b on a.id=b.id where a.age>20 and b.cid=1
上面的语句会自动优化为如下所示:
select * from (select * from table1 where age>20) a join (select * from table2 where cid=1) b on a.id=b.id
就是在子查询阶段就提前将数据进行过滤,后期join的shuffle数据量就大大减少。
- 列裁剪案例:
select a.name, a.age, b.cid from (select * from table1 where age>20) a join (select * from table2 where cid=1) b on a.id=b.id
上面的语句会自动优化为如下所示:
select a.name, a.age, b.cid from (select name, age, id from table1 where age>20) a join (select id, cid from table2 where cid=1) b on a.id=b.id
就是提前将需要的列查询出来,其他不需要的列裁剪掉。
- 常量累加:
select 1+1 as id from table1
上面的语句会自动优化为如下所示:
select 2 as id from table1
就是会提前将1+1
计算成2
,再赋给id列的每行,不用每次都计算一次1+1
。
2. CBO:基于代价的优化
就是在SparkPlanner对优化后的逻辑计划生成了多个可以执行的物理计划Physical Plan之后,多个物理执行计划基于Cost Model选取最优的执行耗时最少的那个物理计划。