目录
背景
优化规则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限制。