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

2022-04-25 15:44:17 浏览数 (1)

目录

背景

优化规则HivePointLookupOptimizerRule

  • matches方法逻辑详解
  • onMatch方法逻辑详解

总结

背景

这篇文章来讲优化规则HivePointLookupOptimizerRule点查找优化规则,主要功能此优化将要应用到Filter过滤表达式上,如果他的表达式包含一个OR操作,且它的子表达式是常量表达式,优化器将会产生一个IN表达式来替代(这样效率更高),如果此OR操作又包含AND子操作可能使用struct结构来产生一个In子句。

此外,规则内设置minNumORClauses最小Or连接个数限制,如果Or个数太少优化空间不大。做转换优化的操作符树如下:

优化规则HivePointLookupOptimizerRule

1)matches方法逻辑详解

matches方法返回此规则Rule是否可能与给定的操作数operands匹配,但是此方法的任何实现都可以给出误报,也就是说虽然规则与操作数匹配,但随后具OnMatch(ReloptRuleCall)而不生成任何后续任务。

判断由RelOptCall调用的优化规则Rule是否与输入参数RelNode关系表达式匹配,即此优化规则Rule能否应用到一个RelNode关系表达式树上。

但此matches方法是继承自父类方法,默认返回true。

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

2)onMatch方法逻辑详解

接收有关一条规则匹配的通知。同时此方法被调用,call.rels保存了与规则Rule的操作数Operands匹配上的关系表达式RelNode集合;call.rels[0]是根表达式。通常一条规则Rule会检查这些节点是否有效匹配,创建一个新表达式RelNode(等价的)然后调用RelOptRuleCall.transformTo(org.apache.calcite.rel.RelNode, java.util.Map<org.apache.calcite.rel.RelNode, org.apache.calcite.rel.RelNode>)注册表达式。而RelOptRuleCall用一系列RelNode关系表达式集合作为参数,对RelOptRule优化规则的调用。

onMatch做RelNode关系表达式树相对复杂,涉及到对这个RelNode操树的遍历、转换、合并等操作。使用两次RexShuttle继承实现的RexTransformIntoInClause转换为IN clause语句类和RexMergeInClause合并IN clause语句类并有返回结果的访问器模式的遍历器,即对一颗操作符树遍历操作。

但实现逻辑较明确大致分为四个步骤:

  • 对Filter过滤器操作进行遍历,找到可转换的点,即OR连接的谓词表达式中的常量收集。如a = 1 or a = 3 or...
  • 合并IN clause 语句,即把上述a = 1 or a = 3 or ...谓词表达式,转为a in (1,3...)
  • 比较Filter的谓词条件部分变换前和变换后是否相同,即真正满足优化规则并做Filter谓词表达式的优化,否则推出优化。
  • 使用变换后的谓词表达式创建新Filter操作,并进行优化转换
代码语言:javascript复制
public void onMatch(RelOptRuleCall call) {
  final Filter filter = call.rel(0);
  final RexBuilder rexBuilder = filter.getCluster().getRexBuilder();
  final RexNode condition = RexUtil.pullFactors(rexBuilder, filter.getCondition());
  // 1. 对Filter过滤器操作进行遍历,找到可转换的点
  RexTransformIntoInClause transformIntoInClause = new RexTransformIntoInClause(rexBuilder, filter,minNumORClauses);
  RexNode newCondition = transformIntoInClause.apply(condition);
  // 2. 合并IN clause 语句
  RexMergeInClause mergeInClause = new RexMergeInClause(rexBuilder);
  newCondition = mergeInClause.apply(newCondition);
  // 3. 比较Filter的谓词条件部分变换前和变换后是否相同
  if (newCondition.toString().equals(condition.toString())) {
    return;
  }
  // 4. 使用变换后的谓词表达式创建新Filter操作,并进行优化转换
  RelNode newFilter = filter.copy(filter.getTraitSet(), filter.getInput(), newCondition);
  call.transformTo(newFilter);
}

对关键步骤的大致实现讲解:

对Filter过滤器操作进行遍历,找到可转换的点,合并IN clause 语句,即把上述a = 1 or a = 3 or ...谓词表达式,转为a in (1,3...)。

RexCall是Calcite中的通过调用运算符而形成的表达式,其中零个或多个表达式作为操作数。如 A = 1 AND B = 2运算符可以是二进制的、一元的、函数的、特殊的语法结构,如CASE ... WHEN ... END,甚至内部生成的构造,如隐式类型转换。运算符的语法实际上是不相关的,因为行表达式(与SQL表达式不同)不直接表示一段源代码。

  • RexCall的连接操作符为AND:

RexUtil.flattenAnd方法把RexCall对象的表达式,以AND节点为把表达式分解为RexNode列表operands,NUll则忽略。

遍历operands如果每个子表达式再含有Or连接,transformIntoInClauseCondition遍历此表达式是否含有形如,a = 1 或 2= a 的Or连接的表达式,则转换MultiMap<a,<1,2>>的对象,便于转换a in (1,2)的IN 语句。

  • RexCall的连接操作符为OR:

可直接使用transformIntoInClauseCondition遍历此表达式,递归遍历地查找并转换。

代码语言:javascript复制
public RexNode visitCall(RexCall call) {
  RexNode node;
  switch (call.getKind()) {//调用 带有操作符的 表达式
    case AND:
      ImmutableList<RexNode> operands = RexUtil.flattenAnd(((RexCall) call).getOperands());//转换为AND连接 RexNode行表达式列表
      List<RexNode> newOperands = new ArrayList<RexNode>();
      for (RexNode operand: operands) {//遍历上述的 行表达式,如果一个行表达式,再含有Or操作符
        RexNode newOperand;
        if (operand.getKind() == SqlKind.OR) {
          try {
            newOperand = transformIntoInClauseCondition(rexBuilder,
                    filterOp.getRowType(), operand, minNumORClauses);//经过变换后,转换IN
            if (newOperand == null) {
              newOperand = operand;
            }
          } catch (SemanticException e) {
            LOG.error("Exception in HivePointLookupOptimizerRule", e);
            return call;
          }
        } else {//不含Or直接,作为新newOperand
          newOperand = operand;
        }
        newOperands.add(newOperand);//新操作数列表
      }
      node = RexUtil.composeConjunction(rexBuilder, newOperands, false);//合并And连接
      break;
    case OR:
      try {//如果Or连接,直接返回call
        node = transformIntoInClauseCondition(rexBuilder,
                filterOp.getRowType(), call, minNumORClauses);
        if (node == null) {
          return call;
        }
      } catch (SemanticException e) {
        LOG.error("Exception in HivePointLookupOptimizerRule", e);
        return call;
      }
      break;
    default:
      return super.visitCall(call);
  }
  return node;
}

transformIntoInClauseCondition把Or连接谓词表达式转换为IN clause语句的实现:

RexUtil.flattenOr方法,以OR节点为把表达式分解为RexNode列表。并把形如字段 a=1 or a=3 or a= 8语句转换为 a in (1,3,8)语句。

同时此方法转换需要满足一定的条件限制:

  • 1、Or连接的个数小于 目标最小Or数,退出优化
  • 2、谓词表达式必须等值连接,“=” 如 a = 1 ,否则退出优化,如a > 1
  • 3、相同字段名称的 Or 常量,不等于 加入常量的个数,则退出优化。
代码语言:javascript复制
private static RexNode transformIntoInClauseCondition(RexBuilder rexBuilder, RelDataType inputSchema,
        RexNode condition, int minNumORClauses) throws SemanticException {
  assert condition.getKind() == SqlKind.OR;

  // 1. We extract the information necessary to create the predicate for the new
  //    filter
  ListMultimap<RexInputRef,RexLiteral> columnConstantsMap = ArrayListMultimap.create();
  ImmutableList<RexNode> operands = RexUtil.flattenOr(((RexCall) condition).getOperands());//分解为Or连接的RexNode集合
  if (operands.size() < minNumORClauses) {//Or连接的个数小于 目标最小Or数,退出优化。
    // We bail out
    return null;
  }
  for (int i = 0; i < operands.size(); i  ) {//遍历RexNode
    final List<RexNode> conjunctions = RelOptUtil.conjunctions(operands.get(i));//每个RexNode元素,再转换为AND连接的列表
    for (RexNode conjunction: conjunctions) {
      // 1.1. If it is not a RexCall, we bail out
      //如果不是表达式调用,则推出优化
      if (!(conjunction instanceof RexCall)) {
        return null;
      }
      // 1.2. We extract the information that we need
      RexCall conjCall = (RexCall) conjunction;
      if(conjCall.getOperator().getKind() == SqlKind.EQUALS) { //如果调用为"=" 表达式 ,如果 a=1
        if (conjCall.operands.get(0) instanceof RexInputRef &&
                conjCall.operands.get(1) instanceof RexLiteral) {
          RexInputRef ref = (RexInputRef) conjCall.operands.get(0);
          RexLiteral literal = (RexLiteral) conjCall.operands.get(1);
          columnConstantsMap.put(ref, literal);//加入 字段,常量值映射
          if (columnConstantsMap.get(ref).size() != i 1) {
            // If we have not added to this column before, we bail out
            return null;
          }
          //如果调用为"=" 表达式 ,如果 1 = a情况
        } else if (conjCall.operands.get(1) instanceof RexInputRef &&
                conjCall.operands.get(0) instanceof RexLiteral) {
          RexInputRef ref = (RexInputRef) conjCall.operands.get(1);
          RexLiteral literal = (RexLiteral) conjCall.operands.get(0);
          columnConstantsMap.put(ref, literal);//加入 字段,常量值映射
          if (columnConstantsMap.get(ref).size() != i 1) {//相同字段名称的 Or 常量,不等于 加入的个数,则推出优化,a=1 or a=3 or a= 8
            // If we have not added to this column before, we bail out
            return null;
          }
        } else {
          // Bail out
          return null;
        }
      } else {
        return null;
      }
    }
  }

之后,还会对各个谓词表达式解析出IN clause进行一次合并操作。

总结

优化规则HivePointLookupOptimizerRule点查询优化,从SQL语句上OR连接的等值谓词,转换为IN语句,这条优化规则相对容易理解。但是底层实现还蛮复杂的,此文章没大量的贴代码而是对关键代码进行讲解,需更多详解了解的,请自行翻阅源码。

0 人点赞