查看原文
其他

Hive优化器原理与源码解析系列--优化规则HiveJoinAddNotNullRule(十二)

后羿BigDataplus BigDataplus 2021-10-15


目录


背景

优化规则HiveJoinAddNotNullRule

  • matches方法逻辑详解

  • onMatch方法逻辑详解

总结


背景

        此篇文章讲解HiveJoinAddNotNullRule优化规则,此优化规则Rule主要功能是将SQL语句中Inner Join关联时,出现在关联条件中的字段存在为null可能的字段,都加上相应字段 is not null条件限制。

        当然在onMatch函数中,也会对优化规则是否可应用莫RelNode做了更多的限制,也不是对所有在On关联条件中应用的字段都会默默地加上IS NOT NULL限制条件的。关联谓词条件恒为true,这样inner join 就变成笛卡尔积了,这种不会做任何的优化。

        无论用户怎么写SQL,优化器都会默默补全成完整的限制条件,同时也由此可见,Inner join 的关联on条件限制中是不支持null匹配的。

        同样,此条优化规则也不例外,也继承自父类RelOptRule来实现的。这里先讲述一下RelOptRule相关概念。

        RelOptRule Calcite框架中的优化规则Rule的抽象类,功能就是把一个关系表达式RelNode1转换为另一个关系表达式RelNode2,它有一系列RelOptRuleOperands,其决定了此Rule是否能被应用到一棵RelNodes操作符数的指定部分section,由optimizer优化器指出哪些Rule是可应用的,然后在这些Rules规则上调用onMatch(RelOptRuleCall)方法。而RelOptRuleCall是优化规则调用,其使用一系列RelNode关系表达式集合作为参数,对RelOptRule优化规则的调用。匹配上优化规则内一系列RelOptRuleOperands操作数,也代表了优化规则Rule配上了输入参数RelNode树的某些子RelNode,可进行优化。


优化规则HiveJoinAddNotNullRule

        优化器的优化规则Rule实现,都需实现两个方法matches和OnMatch两个方法。

1)matches方法逻辑详解

        matches方法返回此规则Rule是否可能与给定的操作数operands匹配。优化器在匹配上规则Rule的所有操作数Operands之后和调用OnMatch(ReloptRuleCall)之前调用此方法。在优化器的实现中,它可能会在调用OnMatch(ReloptRuleCall)之前将匹配的ReloptRuleCall排队很长时间,matches方法提前判断这种方法是有好处的,因为优化器可以在处理的早期,把不满足匹配条件的规则放弃掉。

        判断由RelOptCall调用的优化规则Rule是否与输入参数RelNode关系表达式匹配,即此优化规则Rule能否应用到一个RelNode关系表达式树上。但此matches方法是继承自父类方法,默认返回true。

public boolean matches(RelOptRuleCall call) { return true;}


2)onMatch方法逻辑详解

        此方法是优化规则对RelNode做优化等价变换的关键。实现了getNotNullConditions方法,把RelNode中所引用的字段的索引列表和字段名称的代表的RexNode行表达式列表中,存在可能为空的字段,都加上IS_NOT_NULL的条件限制,并返回相应的RexNode行表达式列表。这也是此优化规则对RelNode树做变换的关键。

private static List<RexNode> getNotNullConditions(RelOptCluster cluster, RexBuilder rexBuilder, RelNode input, Set<Integer> inputKeyPositions, Set<String> pushedPredicates) { final List<RexNode> newConditions = Lists.newArrayList(); for (int pos : inputKeyPositions) {//遍历输入字段的索引位置,并从行类型的字段列表获取盖RelDataType是否为可空,如果可不空,则跳过 RelDataType keyType = input.getRowType().getFieldList().get(pos).getType(); // Nothing to do if key cannot be null if (!keyType.isNullable()) { continue; } RexNode cond = rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, rexBuilder.makeInputRef(input, pos));//给字段引用添加 is not null限制,生成新的RexNode表达式 String digest = cond.toString(); if (pushedPredicates.add(digest)) {//如果pushedPredicates不存在,则天道新条件行表达式RexNode列表中,返回 newConditions.add(cond); } } return newConditions;}

        通过参数inputKeyPositions和pushedPredicates分别为关联条件谓词引用RexNode在schema的索引位置,和中文描述列表,通过变换把存在可能为null的字段,添加IS_NOT_NULL限制生成新RexNode,添加到newConditions,作为新的关联条件RexNode列表返回。

        虽然此条规则中,matches方法默认是返回ture。但在此onMatch方法中,也可做一些是否满足优化规则条件的判断。

满足此优化条件的如下:

  • JOIN关联类型为INNER内关联

  • 必须含有关联条件,并ON关联条件不能恒为true,否则就变成笛卡尔积。

        首先,获取Join对象,并获取关联左右两侧的输入RelNode对象。并判断关联类型是否为INNER JOIN,否则return不做任何优化。其次,或判断Join对象的关联条件,如果isAlwaysTrue恒为true,这就相当于笛卡尔积了,也不做任何优化。

final Join join = call.rel(0);RelNode lChild = join.getLeft();RelNode rChild = join.getRight();HiveRulesRegistry registry = call.getPlanner().getContext().unwrap(HiveRulesRegistry.class);assert registry != null;if (join.getJoinType() != JoinRelType.INNER) {//关联条件不是inner join内连接,将会不会做任何优化 return;}if (join.getCondition().isAlwaysTrue()) {//join的关联条件判断是否一直为true,如果恒为true,类似笛卡尔积,则也不会做任何优化 return;}


        获取JoinPredicateInfo关联谓词信息对象,如果出现语义异常,同样返回return结束,不做任何优化。

JoinPredicateInfo joinPredInfo;try { joinPredInfo = HiveCalciteUtil.JoinPredicateInfo.constructJoinPredicateInfo(join);} catch (CalciteSemanticException e) { return;}

        JoinPredicateInfo:join谓词信息,表现为Join关联条件时,使用JoinLeafPredicateInfo叶子结点谓词信息来表示谓词中单个关联元素。如:JoinPredicateInfo = JoinLeafPredicateInfo1 and JoinLeafPredicateInfo2。包含如下

  • 为等值关联equi-join(equiJoinPredicateElements),保留了关联元素的顺序

  • 保存了等值连接join的左右子RelNode的投影索引,这些索引都在join relNode的schema中。

  • 保存了join keys的投影索引与连接元素的JoinLeafPredicateInfo映射关系

        从上述已获取JoinPredicateInfo对象获取join的等值谓词信息元素在schema中索引信息,左右两侧的分别存入joinLeftKeyPositions和joinRightKeyPositions集合。

Set<Integer> joinLeftKeyPositions = new HashSet<Integer>();Set<Integer> joinRightKeyPositions = new HashSet<Integer>();for (int i = 0; i < joinPredInfo.getEquiJoinPredicateElements().size(); i++) { JoinLeafPredicateInfo joinLeafPredInfo = joinPredInfo. getEquiJoinPredicateElements().get(i); joinLeftKeyPositions.addAll(joinLeafPredInfo.getProjsFromLeftPartOfJoinKeysInChildSchema());//提取出使用到谓词在schema中的索引 joinRightKeyPositions.addAll(joinLeafPredInfo.getProjsFromRightPartOfJoinKeysInChildSchema());}

        使用getNotNullConditions(文章开头讲过)分别对左右两侧的谓词引用元素,再分别生成新的不null的条件列表newLeftConditions和newRightConditions。

// Build not null conditionsfinal RelOptCluster cluster = join.getCluster();final RexBuilder rexBuilder = join.getCluster().getRexBuilder();
Set<String> leftPushedPredicates = Sets.newHashSet(registry.getPushedPredicates(join, 0));final List<RexNode> newLeftConditions = getNotNullConditions(cluster, rexBuilder, join.getLeft(), joinLeftKeyPositions, leftPushedPredicates);Set<String> rightPushedPredicates = Sets.newHashSet(registry.getPushedPredicates(join, 1));final List<RexNode> newRightConditions = getNotNullConditions(cluster, rexBuilder, join.getRight(), joinRightKeyPositions, rightPushedPredicates);//在join右侧输入RelNode,根据在schema中索引信息,提取对应索引对应的RexNode表达式,存放到行表达式的List,便于下面使用
// Nothing will be added to the expressionRexNode newLeftPredicate = RexUtil.composeConjunction(rexBuilder, newLeftConditions, false); //把所有谓词都用and连接起来RexNode newRightPredicate = RexUtil.composeConjunction(rexBuilder, newRightConditions, false);if (newLeftPredicate.isAlwaysTrue() && newRightPredicate.isAlwaysTrue()) { return;}

        把新生成的条件RexNode列表,用RexUtil.composeConjunction方法用AND连接起来分别为newLeftPredicate和newRightPredicate,整体判断是否问恒为true。如果为真,则不做任何优化。如果都不恒为真,并把新的谓词信息创建Filter并复制到原lChild和rChild对象上。

if (!newLeftPredicate.isAlwaysTrue()) {//如果谓词表达式不恒为true RelNode curr = lChild; lChild = filterFactory.createFilter(lChild, newLeftPredicate);//创建带有谓词的join左侧输入RelNode call.getPlanner().onCopy(curr, lChild);//}if (!newRightPredicate.isAlwaysTrue()) { RelNode curr = rChild; rChild = filterFactory.createFilter(rChild, newRightPredicate); call.getPlanner().onCopy(curr, rChild);}

        使用join.copy方法,用关联条件中引用的谓词元素,可能为null的都添加了IS_NOT_NULL判断后新生成的条件,生成新的Join对象newJoin,再把newJoin和谓词信息组册到HiveRulesRegistry对象,此类在整个优化规则使用过程中,起到很作用的作用,主要功能:

  • rule规则与relnode关系节点的map映射

  • relnode与相关表达式(字符串表示)集合Set

两种关系集合的封装,最后把newJoin注册优化器。

Join newJoin = join.copy(join.getTraitSet(), join.getCondition(), lChild, rChild, join.getJoinType(), join.isSemiJoinDone());//从新创建新Joincall.getPlanner().onCopy(join, newJoin);//当关系表达式复制到类似表达式时调用
// Register information about created predicatesregistry.getPushedPredicates(newJoin, 0).addAll(leftPushedPredicates);registry.getPushedPredicates(newJoin, 1).addAll(rightPushedPredicates);call.transformTo(newJoin);

join.isSemiJoinDone() 判断LogicalJoin 是否已由JoinAddRedundantSemiJoinRule规则优化成了SemiJoin,默认是false。因为hive2.3还没有此条规则。


总结

        通过对HiveJoinAddNotNullRule优化规则源码解读,可知道了Inner join不是支持null值连接的,优化器在生成执行计划时,默默地把引用的可能为null的谓词加上IS_NOT_NULL限制。

        由于笔者知识及水平有限,因此文中错漏之处在所难免,恳请各位老师、专家不吝赐教。



往期文章分享


优化规则系列

Hive优化器原理与源码解析系列--优化规则SortRemoveRule(一)

Hive优化器原理与源码解析系列--优化规则SortJoinReduceRule(二)

Hive优化器原理与源码解析系列--优化规则SortProjectTransposeRule(三)

Hive优化器原理与源码解析系列--优化规则SortUnionReduceRule(四)

Hive优化器原理与源码解析系列--优化规则SortMergeRule(五)

Hive优化器原理与源码解析系列--优化规则ProjectFilterPullUpConstantsRule(六)

Hive优化器原理与源码解析系列--优化规则SortLimitPullUpConstantsRule(七)

Hive优化器原理与源码解析系列--优化规则UnionPullUpConstantsRule(八)

Hive优化器原理与源码解析系列--优化规则ProjectOverIntersectRemoveRule(九)

Hive优化器原理与源码解析系列--优化规则ProjectSortTransposeRule(十)

Hive优化器原理与源码解析系列--优化规则HiveProjectMergeRule(十一)

成本模型系列

Hive优化器原理与源码解析系列—统计信息带谓词选择率Selectivity

Hive优化器原理与源码解析系列—统计信息之选择性

Hive优化器原理与源码解析系列—统计模块内存成本估算

Hive优化器原理与源码解析系列--统计信息中间结果大小计算

Hive优化器原理与源码解析系列—CBO成本模型CostModel(一)

Hive优化器原理与源码解析系列—CBO成本模型CostModel(二)

Hive优化器原理与源码解析系列—统计信息UniqueKeys列集合

Hive优化器原理与源码解析—统计信息Parallelism并行度计算

Hive优化器原理与源码解析—统计信息NDV唯一值数估算


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存