目录
背景
优化规则HiveProjectMergeRule
- matches方法逻辑详解
- onMatch方法逻辑详解
总结
背景
此篇文章讲解HiveProjectMergeRule优化规则,顶层Project投影操作(相当于HSQL中的Select操作)和底部Project投影操作进行合并的优化规则,但前提是这些Project不投影相同的输入引用集。此优化规则中,Hive只实现了matches匹配方法的判断逻辑部分,不支持在RelNode关系表达式树中含有Window窗口函数或Hive各种分析函数的的Project投影操作,而相关逻辑判断和优化的等价变换的RelNode关系表达式树的OnMatch函数是直接从Calcite 优化器优化规则父类ProjectMergeRule继承而来的。
学习和研究CBO优化器相关知识,对于初学者来说,稍微有一点点门槛。没入门之前总觉得优化器做的都是高大上的优化操作。其实优化器也做稍微熟练SQL开发者都能优化的事情,毕竟一款支持SQL数据库面对象的是各个层次开发者,所以优化器无论是简单和复杂的优化操作都得具备。就像此条优化规则HiveProjectMergeRule就像类似于一段SQL的内外层两个Select 操作进行合并减少重复操作,从而达到优化的目的。
同样,此条优化规则也不例外,也继承自父类RelOptRule来实现的。这里先讲述一下RelOptRule相关概念。
RelOptRule Calcite框架中的优化规则Rule的抽象类,功能就是把一个关系表达式RelNode1转换为另一个关系表达式RelNode2,它有一系列RelOptRuleOperands,其决定了此Rule是否能被应用到一棵RelNodes操作符数的指定部分section,由optimizer优化器指出哪些Rule是可应用的,然后在这些Rules规则上调用onMatch(RelOptRuleCall)方法。而RelOptRuleCall是优化规则调用,其使用一系列RelNode关系表达式集合作为参数,对RelOptRule优化规则的调用。匹配上优化规则内一系列RelOptRuleOperands操作数,也代表了优化规则Rule配上了输入参数RelNode树的某些子RelNode,可进行优化。
优化规则HiveProjectMergeRule
优化器的优化规则Rule实现,都需实现两个方法matches和OnMatch两个方法。
1)matches方法逻辑详解
matches方法返回此规则Rule是否可能与给定的操作数operands匹配。优化器在匹配上规则Rule的所有操作数Operands之后和调用OnMatch(ReloptRuleCall)之前调用此方法。在优化器的实现中,它可能会在调用OnMatch(ReloptRuleCall)之前将匹配的ReloptRuleCall排队很长时间,matches方法提前判断这种方法是有好处的,因为优化器可以在处理的早期,把不满足匹配条件的规则放弃掉。
判断由RelOptCall调用的优化规则Rule是否与输入参数RelNode关系表达式匹配,即此优化规则Rule能否应用到一个RelNode关系表达式树上。
当前Project投影操作中不支持一个窗口函数与另一个窗口函数的合并。即顶部Project投影操作中RexNode行表达式的序号位,对应与底部Project的相应的序号RexNode行表达式都是窗口函数,则matches返回false。RexOver继承RexCall表达式用来调用窗口Window函数的实现类,如果一个RexNode表达式是RexOver实例,则说明RexNode行表达式为窗口函数。
RexOver继承关系如下:
代码语言:javascript复制public class RexOver
extends RexCall
Call to an aggregate function over a window.
其他默认返回true
代码语言:javascript复制public boolean matches(RelOptRuleCall call) {
//当前投影合并,不支持窗口函数
final Project topProject = call.rel(0);//顶部Project操作
final Project bottomProject = call.rel(1);//底部Project操作
for (RexNode expr : topProject.getChildExps()) {//遍历顶部Project的输入行表达式
if (expr instanceof RexOver) {
Set<Integer> positions = HiveCalciteUtil.getInputRefs(expr);//返回当前字段或行表达式中索引的位置
for (int pos : positions) {//顶Project中相应字段对应的位置来查找在底部Project投影中到行表达式,判读是否为窗口函数
if (bottomProject.getChildExps().get(pos) instanceof RexOver) {
return false;
}
}
}
}
return super.matches(call);
}
总之,顶层Project中一个窗口函数中的字段来自底部Project中窗口函数中的字段,是不支持的。
2)onMatch方法逻辑详解
同样,首先使用RelOptRuleCall对象rel(0)方法获取根RelNode关系表达式Project topProject,其次获取Project的子RelNode关系表达式bottomProject。
代码语言:javascript复制 final Project topProject = call.rel(0);
final Project bottomProject = call.rel(1);
final RelBuilder relBuilder = call.builder();
虽然在matches方法内优化规则Rule能否应用做了判断,但不会完全覆盖到,所以onMatch方法内也可做相应的判断,如果不满足条件也不会做任何优化变换而结束。
然后分别获取的顶部和底部的Project投影操作的Permutation对象。如果对象非空并是isIdentity为true,不再做任何优化return结束。
如果上述条件都不满足的话,则通过topPermutation.product(bottomPermutation)方法,通过分析序列化输出创建新的Permutation对象的字段Mapping(可参考前面Rule(十)何为Mapping),取出Permutation对象的字段和相应的数据类型,再使用上面push的底部Project操作子输入RelNode,然后构建器出新合并生成的Project投影操作等价变换后,注册等价关系表达式集合RelSet。
代码语言:javascript复制final Permutation topPermutation = topProject.getPermutation();
if (topPermutation != null) {
if (topPermutation.isIdentity()) {
// Let ProjectRemoveRule handle this.
return;
}
final Permutation bottomPermutation = bottomProject.getPermutation();
if (bottomPermutation != null) {
if (bottomPermutation.isIdentity()) {
// Let ProjectRemoveRule handle this.
return;
}
//通过分析序列化输出创建Project
final Permutation product = topPermutation.product(bottomPermutation);
relBuilder.push(bottomProject.getInput());
relBuilder.project(relBuilder.fields(product),
topProject.getRowType().getFieldNames());
call.transformTo(relBuilder.build());
return;
}
}
以下是继续对一棵关系表达式树是否满足匹配优化的条件判断。
在此优化规则内,force是判断Project合并是否为强制模式,如果force=true即强制模式,即使顶层Project和底部Project完全相同,也会执行合并动作。如果force=false即非强制模式,顶部和底部Project相同,则不会再做任何优化操作。RexUtil.isIdentity方法是判断两个表达式集合的个数和数据类型是否完全一致。
代码语言:javascript复制if (!force) {
if (RexUtil.isIdentity(topProject.getProjects(),
topProject.getInput().getRowType())) {
return;
}
}
RelOptUtil.pushPastProject方法把顶部Project投影内RexNode行表达式和底部Project投影内RexNode行表达式进行合并成新的Project对象。
代码语言:javascript复制//将基于Project投影输出字段的表达式列表转换为Project投影输入字段上的等效表达式。
static java.util.List<RexNode> pushPastProject(java.util.List<? extends RexNode> nodes, Project project)
即使顶部和底部Project操作合并后生成新的Project投影操作newProjects,使用RexUtil.isIdentity判断newProjects与底部Project的输入子RelNode做一次是否相同,如果是强制模式即force=true,直接调用call.transformTo把底部Project投影的子输入RelNode注册RelSet集合内,做一次等价变换然后返回return结束。
代码语言:javascript复制final List<RexNode> newProjects =
RelOptUtil.pushPastProject(topProject.getProjects(), bottomProject);//想等于Project进行合并。生成新的Project
final RelNode input = bottomProject.getInput();
if (RexUtil.isIdentity(newProjects, input.getRowType())) {//如果新生成的Project和底Project的输入input是否相等。
if (force
|| input.getRowType().getFieldNames()
.equals(topProject.getRowType().getFieldNames())) {//如果是强制模式,字段也相等,直接把底层Project子输入,注册到优化器
call.transformTo(input);
return;
}
}
// replace the two projects with a combined projection
relBuilder.push(bottomProject.getInput());//使用了底层Project的子input push到构建器,想等于直接移除底层Project
relBuilder.project(newProjects, topProject.getRowType().getFieldNames());//使用合并的Project到构建器,做了合并Project变换
call.transformTo(relBuilder.build());
在最后,排除两者Project操作相同后,bottomProject.getInput()压入构建器内作为顶层Project操作的子RelNode,使用relBuilder.project方法把newProjects生成新的合并后的Project投影操作,注册到等价的关系表达式集合RelSet。
总结
HiveProjectMergeRule优化规则的优化功能相对还较简单的,把上下两个Project投影操作(RelNode操作符树来讲的上下关系),从Sql语句来说,把内外层两个Select进行合并一个Select的优化操作过程,本篇文章从原理和源码进行解析此规则是如何实现的。