前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Hive优化器原理与源码解析系列--优化规则HiveJoinAddNotNullRule(十二)

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

作者头像
用户7600169
发布2022-04-25 15:35:41
4310
发布2022-04-25 15:35:41
举报
文章被收录于专栏:BigDataplusBigDataplus

目录

背景

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BigDataplus 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档