深入分析Spark SQL Catalyst Analyzer

323 阅读4分钟

图片

在SparkSQLParser将一个SQL查询文本解析成ANTLR树,然后再解析成逻辑计划后,此刻的逻辑计划是未解决的。表名和列名只是未识别的文本字符。Spark SQL引擎不知道这些表名或列名如何映射到底层数据库对象。Catalyst提供了一个逻辑计划分析器,它查找SessionCatalog并将UnresolvedAttributes和UnresolvedRelation翻译成完全类型的对象(请看下图彩色部分)。图片除了关系和属性解析之外,Catalyst Analyzer还负责验证Spark查询的语法正确性,并进行语法级运算符和表达式分析。如上一篇文章所述,LogicalPlan在内部表示为Catalyst中的树类型。Catalyst分析器定义了一组分析规则,这些规则将与逻辑计划树中的节点进行模式匹配,并将分析规则应用于匹配的节点。图片在Spark 3.0.0中,Catalyst Analyzer带有51条内置规则,这些规则被组织成12批。在这篇博文的其余部分,我将重点介绍其中两个最重要的规则,即ResolveRelations和ResolveReferences,但也会选择一些其他有代表性的规则来介绍。图片ResolveRelations规则ResolveRelations规则用目录中的关系(relations)替换了UnresolvedRelations。以下面这个Spark SQL查询为例,查询中涉及两个表,[sales]和[items]。SparkSQLParser将这两个表解析为两个UnresolvedRelations。图片图片ResolvedRelation规则需要做的是将UnresolvedRelations转换为具有完整类型信息的关系。正如你在下面的快照中所看到的,需要从存储在SessionCatalog中的表元数据中解析出完全合格的数据表名称和表的列名。图片当resovledconnection规则应用于我们的查询(LogicalPlan)时,resolveOperatorsUp调用逻辑计划的方法,自下而上遍历所有节点并调用lookupRelation方法UnresolvedRelations节点。lookupRelation方法从会话目录中查找UnresolvedRelation的具体关系。CatalogV2Util的loadTable方法通过UnresolvedRelation的标识符加载表元数据,并返回一个Table类型,它代表一个数据sourice的逻辑结构化数据集,比如文件系统中的一个目录,Kafka的一个topic,或者目录中的一个表。图片如果在目录中发现V1表,就会调用V1版本会话目录(catalogManager.v1SessionCatalog)中的getRelation方法,该方法在关系上创建一个View操作符,并将其包装在一个跟踪视图名称的SubqueryAlias中。如果在目录中找到V2表,就会创建一个DataSourceV2Relation对象,该对象也会被跟踪关系名称的SubqueryAlias所包裹。SubqueryAlias是一个单数逻辑运算符,它承载着它所包装的子查询的别名,可以被查询的其他部分所引用。图片ResolveReferences规则ResolveReferences 规则用来自逻辑计划节点的子节点的具体 AttributeReferences 替换 UnresolvedAttributes。当 ResolveReferences 规则被应用到逻辑计划时,resolveOperatorsUp 函数会自下而上地遍历并将该规则应用到逻辑计划的节点。只有当一个节点的所有子节点都被解决时,该规则才会被应用。因此,你可以看到,一个有多层子节点的逻辑计划不能在规则的一次运行中得到解决,而是需要多次运行,直到逻辑节点树达到固定点。图片在我们上面提到的SQL查询例子中,ResolveRelations规则已经将UnresolvedRelation [sales]和[items]解决为已解决的关系,并解决了对基础数据源列的属性引用。当ResolveReferences规则被应用时,只有"Join"节点的子节点被解决了,所以只有"Join"节点的未解决的属性引用被解决了。在当前的迭代运行中,"Filter"节点没有资格被解决。图片在下一次迭代运行中,"Join"节点已经被标记为已解决,并且满足在"Filter"节点上应用ResolveReferences规则的要求。现在 "i_prece"属性可以被解析。图片在内部,要解析的当前逻辑计划的mapExpression方法被调用,以便在逻辑计划的所有表达式上运行 resolveExpressionTopDown 函数。如果任何表达式是 UnresolvedAttribute 类型,未解决的属性的名称部分将使用这个逻辑计划的所有子节点的输入被解析为 NamedExpression。图片

CTESubstitution规则

CTESubstitution规则用于分析WITH SQL语法,该语法将一个WITH节点替换为子查询。CTESubstitution规则的应用方法调用traverseAndSubstituteCTE方法,该方法从最后一个到第一个替换CTE定义,因为一个CTE定义可以引用前一个定义。从下面的例子中,我们可以看到引用CTE定义的UnresolvedRelation被CTE定义的逻辑计划所取代。图片图片图片ResolveJoinStrategyHintsResolveJoinStrategyHints规则通过递归地向下遍历查询计划中与指定的关系别名相匹配的关系或子查询来分析关系别名上的join strategy hint(连接策略提示),并使用ResolvedHint节点来包装它。下面是Spark官方文档中支持的 join Hints types的列表。

Join Hints Types

  • BROADCAST:建议Spark使用broadcast join。不管autoBroadcastJoinThreshold如何,带有hint join的一方将被广播。如果join的两边都有broadcast hints,那么规模较小的那一边(基于统计资料)将被广播。BROADCAST的别名是BROADCASTJOIN和MAPJOIN。
  • MERGE:建议Spark使用shuffle sort merge join。MERGE的别名是SHUFFLE_MERGE和MERGEJOIN。
  • SHUFFLE_HASH:建议Spark使用shuffle hash join。如果双方都有shuffle hash hints,Spark会选择较小的一方(基于统计数字)作为构建方。
  • SHUFFLE_REPLICATE_NL:建议Spark使用shuffle-and-replicate nested loop join(嵌套循环连接)。

以我们上面提到的[sales]和[items]表的join查询为例,选择SortMergeJoin进行join操作。图片我们现在在[items]关系上添加BROADCAST join hint。图片在分析的逻辑计划中,添加了一个策略为广播的ResolvedHint节点,并将其与[items]关系节点包裹起来。图片如果我们检查物理计划,我们可以看到连接类型已经从排序合并改为广播。图片ResolvePivotResolvePivot规则将PIVOT操作符转换为两阶段聚合。该阶段使用由分组表达式属性分组的agg函数(来自SQL的分组表达式是隐含的,需要推导)和pivot列聚合数据集。枢轴列在第一阶段后将只有唯一的值。然后,PivotFrist函数被用来将枢轴列的值重新排列成枢轴的形式。然后调用第二个聚合函数,通过枢轴列聚合这些值。图片以下面的查询为例,Pivot操作符被转换成两个Aggregate操作符,第一个Aggregate使枢轴列'name'包含唯一的值,第二个Aggregate将枢轴值('John'和'Mike')转换为列并在'age'列上进行聚合(sum)。图片图片图片

- THE END -