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

2022-04-25 15:35:41 浏览数 (1)

目录

背景

优化规则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。

代码语言:javascript复制
public boolean matches(RelOptRuleCall call) {
  return true;
}

2)onMatch方法逻辑详解

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

代码语言:javascript复制
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,这就相当于笛卡尔积了,也不做任何优化。

代码语言:javascript复制
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结束,不做任何优化。

代码语言:javascript复制
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集合。

代码语言:javascript复制
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。

代码语言:javascript复制
// Build not null conditions
final 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 expression
RexNode 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对象上。

代码语言:javascript复制
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注册优化器。

代码语言:javascript复制
Join newJoin = join.copy(join.getTraitSet(), join.getCondition(),
        lChild, rChild, join.getJoinType(), join.isSemiJoinDone());//从新创建新Join
call.getPlanner().onCopy(join, newJoin);//当关系表达式复制到类似表达式时调用

// Register information about created predicates
registry.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限制。

0 人点赞