SQL执行过程概览
逻辑计划阶段
会将用户所写的SQL语句转换成树型数据结构(逻辑算子树),SQL语句中蕴含的逻辑映射到逻辑算子树的不同节点,
逻辑计划阶段生成的逻辑算子树并不会直接提交执行,仅作为中间阶段。
逻辑算子树的生成过程经历3个子阶段
1.未解析的逻辑算子树;仅仅是数据结构,不包含任何数据信息等
2.解析后的逻辑算子树;节点中绑定各种信息
3.优化后的逻辑算子树;应用各种优化规则对一些低效的逻辑计划进行转换
物理计划阶段
将上一步逻辑计划阶段生成的逻辑算子树进行进一步转换,生成物理算子树。
物理算子树的节点会直接生成RDD或对RDD进行transformation操作(注:每个物理计划节点中都实现了对RDD进行转换的execute方法)
物理计划阶段的3个子阶段
1.物理算子树的列表;(注:同样的逻辑算子树可能对应多个物理算子树)
2.最优物理算子树;从算子树列表中按照一定的策略选取最优的物理算子树,然后对选取的物理算子树进行提交前的准备工作;例如:确保分区操作正确,物理算子树节点重用,执行代码生成等
3.准备后的物理算子树;对物理算子树生成的RDD执行action操作,即可提交程序
SQL语句的解析一直到提交之前,整个转换过程都在spark集群的Driver端进行不涉及分布式环境。
Catalyst
sparkSQL内部实现流程中平台无关部分的基础框架称为Catalyst,它主要包括InternalRow体系、TreeNode体系和Expression体系。
InternalRow体系
spark SQL 内部实现中,InternalRow就是用来表示一行行数据的类,物理算子树节点产生和转换的RDD类型即为RDD[InternalRow] 。 InternalRow中的每一列都是Catalyst内部定义的数据类型。
InternalRow作为一个抽象类,包含numFields和update方法,以及各列数据对应的get与set方法,InternalRow中都是根据下表来访问和操作列元素的。
其具体的实现包括BaseGenericInternalRow、UnsafeRow和JoinedRow3个直接子类

InternalRow体系
BaseGenericInternalRow:同样是一个抽象类,实现了InternalRow中定义的所有get类型方法,这些方法的实现都通过调用类中定义的genericGet虚函数进行,该函数的实现在下一级子类中(也就是GenericInternalRow 、 SpecificInternalRow 、 MutbaleUnsafeInternalRow类中)
JoinedRow:该类主要用于Join操作,将两个InternalRow放在一起形成新的InternalRow。使用时需要注意构造参数的顺序。
UnsafeRow:不采用java对象存储的方式,避免了JVM中垃圾回收(GC)的代价。此外UnsafeRow对行数据进行了特定的编码,使得存储更加高效。
BaseGenericInternalRow也有3个子类,分别是GenericInternalRow、SpecificInternalRow和 MutableUnsafeRow类。
其中MutableUnsafeRow和UnsafeRow相关,用来支持对特定的列数据进行修改。
GenericInternalRow类源码
//构造参数是Array[Any]类型,采用对象数组进行底层存储、
// 注意:数组是非拷贝的,因此一但创建,就不允许通过set操作进行改变。
class GenericInternalRow(val values: Array[Any]) extends BaseGenericInternalRow {
/** No-arg constructor for serialization. */
protected def this() = this(null)
def this(size: Int) = this(new Array[Any](size))
// 也是直接根据下表访问的
override protected def genericGet(ordinal: Int) = values(ordinal)
override def toSeq(fieldTypes: Seq[DataType]): Seq[Any] = values.clone()
override def numFields: Int = values.length
override def setNullAt(i: Int): Unit = { values(i) = null}
override def update(i: Int, value: Any): Unit = { values(i) = value }
}
而SpecificInternalRow则是以Array[MutableValue]为构造参数的,允许通过set操作进行修改。
final class SpecificInternalRow(val values: Array[MutableValue]) extends BaseGenericInternalRow {
TreeNode体系
无论是逻辑计划还是物理计划,都离不开中间数据结构,在Catalyst中,对应的是TreeNode体系,TreeNode类是Sparksql中所有树结构的基类,TreeNode内部包含一个Seq[BaseType]类型的变量children来表示节点,TreeNode定义了foreach、map、collect等针对节点操作方法,以及transformUp和transformDown等遍历节点并对匹配节点进行相应转换。
TreeNode一直在内存里维护,不会dump到磁盘以文件形式存储,且无论在映射逻辑执行计划阶段还是优化逻辑执行计划阶段,树的修改都是以替换已有节点的方式进行。
TreeNode体系
TreeNode基本操作
除上述操作外,Catalyst中还提供了节点位置功能,即能够根据TreeNode定位到对应的SQL字符串中的行数和起始位置,该功能在SQL解析发生异常时能够方便用户迅速找到出错的地方
// 在TreeNode类中
case class Origin(
line: Option[Int] = None, // 行号
startPosition: Option[Int] = None) // 偏移量
object CurrentOrigin {
private val value = new ThreadLocal[Origin]() {
override def initialValue: Origin = Origin()
}
def get: Origin = value.get()
def set(o: Origin): Unit = value.set(o)
def reset(): Unit = value.set(Origin())
def setPosition(line: Int, start: Int): Unit = {
value.set(
value.get.copy(line = Some(line), startPosition = Some(start)))
}
def withOrigin[A](o: Origin)(f: => A): A = {
set(o)
val ret = try f finally { reset() }
ret
}
}