Hive优化器原理与源码解析系列--优化规则AggregateProjectPullUpConstantsRule(十七)
目录
背景
优化规则AggregateProjectPullUpConstantsRule
matches方法逻辑详解
onMatch方法逻辑详解
总结
背景
这篇文章来讲优化规则AggregateProjectPullUpConstantsRule,顾名思义是将Aggregate汇总操作中常量字段上拉到Project投影操作中的优化规则,主要功能从Aggregate聚合中删除常量键。常量字段是使用RelMetadataQuery.getpulldupredicates(RelNode)推导的,其输入不一定必须是Project投影操作。但此Rule规则从不删除最后一列,简单来讲,如果groupBy字段只有一列,而且为常量,也不会执行此优化,因为聚合Aggregate([])返回1行,即使其输入为空。由于转换后的关系表达式必须与原始关系表达式匹配,为等价变换,因此常量被放置在简化聚合Aggregate上方的Project投影中。
举例说明:
如员工信息表:EMPLOYEE
id ID标识
name 姓名
sex 性别(f:女性 m:男性)
city 城市
待优化前SQL语句:
SELECT
city,
sex,
COUNT(id)
FROM EMPLOYEE
WHERE sex = 'f'
GROUP BY city,sex
通过AggregateProjectPullUpConstantsRule优化规则等价变换后,优化后SQL语句:
SELECT
city,
'f' as sex,
emp_cnt
(
SELECT
city,
COUNT(id) as emp_cnt
FROM EMPLOYEE
WHERE sex = 'f'
GROUP BY city
)
通过从等值谓词中识别GroupBy所引用sex字段值一直为常量'f',于是把Aggregate聚合中GroupBy中sex分组字段移除,在Aggregate操作之上创建一个Project投影,并把GroupBy删除sex常量'f',放置其中,这样就完成了Aggregate操作中常量上拉。
上述这些操作AggregateProjectPullUpConstantsRule优化规则是如何做到的,应用此条规则需要满足哪些条件,接下来详细讲解。
优化规则AggregateProjectPullUpConstantsRule
1)matches方法逻辑详解
matches方法返回此规则Rule是否可能与给定的操作数operands匹配,但是此方法的任何实现都可以给出误报,也就是说虽然规则与操作数匹配,但随后OnMatch(ReloptRuleCall)而不生成任何后续任务。
判断由RelOptCall调用的优化规则Rule是否与输入参数RelNode关系表达式匹配,即此优化规则Rule能否应用到一个RelNode关系表达式树上。但此matches方法是继承自父类方法,默认返回true。
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优化规则的调用。
首先call.rel(0)获取Aggregate操作对象,并取得groupBy引用字段的个数,如果只有GroupBy只有一个字段,已经没有优化的空间,不可能把一个非空groupby转换为空groupBy,即不可能移除仅有一个常量字段。
final Aggregate aggregate = call.rel(0);
final RelNode input = call.rel(1);
final int groupCount = aggregate.getGroupCount();//返回groupBy 字段的个数
if (groupCount == 1) {//如果groupBy仅引用一个字段,则退出优化
return;
}
哪些是常量字段是RelMetadataQuery.getpulldupredicates(RelNode)提取出关于此输入RelNode的谓词,返回RelOptPredicateList对象推导的。
RelOptPredicateList:
已知保存在特定关系表达式输出中的谓词。
上拉谓词:(字段pulldupredicates是应用于关系表达式输出的每一行的谓词。它们是从输入关系表达式和关系运算符推断出来的。
例如,如果将Filter(x>1)应用于谓词y<10的关系表达式,则过滤器的上拉谓词为[y<10,x>1]。
推断谓词:仅适用于联接。如果联接的左输入上有谓词,并且该谓词位于联接条件中使用的列上,则可以在联接的右输入上推断谓词。(反之亦然。)
如:
SELECT *FROM empJOIN dept ON emp.deptno = dept.deptno
WHERE emp.gender = 'F' AND emp.deptno < 10
说明:
左侧: Filter(Scan(EMP), deptno < 10, predicates: [deptno < 10]
右侧: Scan(DEPT), predicates: []关联Join: Join(left, right, emp.deptno = dept.deptno, leftInferredPredicates: [],
右侧推断谓词rightInferredPredicates: [deptno < 10],
上拉谓词pulledUpPredicates: [emp.gender = 'F', emp.deptno < 10, emp.deptno = dept.deptno, dept.deptno < 10]
注意:来自左输入的谓词出现在rightInferredPredicates中。来自多个源的谓词出现在pulledUpPredicates中。
那么RelOptPredicateList对象的表现形式:[emp.gender = 'F', emp.deptno < 10, emp.deptno = dept.deptno, dept.deptno < 10],如果没有从此RelNode提取的谓词为null,则优化无法继续。
ReduceExpressionsRule.predicateConstants方法把RelOptPredicateList对象提取出等值谓词表达式,如上述的emp.gender = 'F'。以<emp.gender,'F'>形式映射存放在变量constants。遍历GroupBy引用字段的索引,并包装成RexInputRef(序号,字段数据类型)代表一个字段。如果在常量等值谓词映射关系中存在的。则以<字段索引,常量值>映射关系存在,如上述的<字段序号,'F'>。
同样,如果GroupBy后没引用常量字段或引用常量字段没有在等值常量谓词中出现,则推出优化。
final RexBuilder rexBuilder = aggregate.getCluster().getRexBuilder();
final RelMetadataQuery mq = RelMetadataQuery.instance();
final RelOptPredicateList predicates =
mq.getPulledUpPredicates(aggregate.getInput());//参数为聚合的子RelNode
if (predicates == null) {//如果没有提取出谓词predicate,则推出优化
return;
}
final ImmutableMap<RexNode, RexNode> constants =
ReduceExpressionsRule.predicateConstants(RexNode.class, rexBuilder,
predicates);
final NavigableMap<Integer, RexNode> map = new TreeMap<>();
for (int key : aggregate.getGroupSet()) {//遍历GroupBy后的字段的序号(index)
final RexInputRef ref =
rexBuilder.makeInputRef(aggregate.getInput(), key);//包装成RexInputRef(序号,字段数据类型)代表 一个字段
if (constants.containsKey(ref)) {//判断是否存在
map.put(key, constants.get(ref));
}
}
if (map.isEmpty()) { //如果groupBy引用的字段,都不是常量,则退出优化
return;
}
if (groupCount == map.size()) {
//如果groupBy个数全是常量项的话,则删除。不能全部上拉
map.remove(map.navigableKeySet().first());
}
最后, 如果groupBy个数全是常量项的话,则删除。但“分组依据”中至少需要一个项目。否则,“GROUP BY 1,2”可能会更改为“GROUP BY()”。移除第一个元素在这里不是最优的,不过,它将允许我们使用下面的快速路径(只需修剪groupCount)。
创建上拉的Aggregate聚合操作,移除聚合中使用的常量。
遍历aggregate.getGroupSet()返回对象GroupBy字段的位图索引,判断如果在常量map中存在,则删除。这也是删除GroupBy常量的关键部分(哪些常量是可以删除,仔细看前面讲过的,生成删除后的新newGroupSet。创建删除常量后的新Aggregate对象。
ImmutableBitSet newGroupSet = aggregate.getGroupSet();//Returns a bit set of the grouping fields.
for (int key : map.keySet()) {
newGroupSet = newGroupSet.clear(key); //清除GroupBy中引用的常量字段,生成新的newGroupSet对象
}
final int newGroupCount = newGroupSet.cardinality();
//如果常量在组列表的后端,我们只需减少组计数。后面默认舍掉
final RelBuilder relBuilder = call.builder();
relBuilder.push(input);
// Clone aggregate calls.
final List<AggregateCall> newAggCalls = new ArrayList<>();
for (AggregateCall aggCall : aggregate.getAggCallList()) {
newAggCalls.add(
aggCall.adaptTo(input, aggCall.getArgList(), aggCall.filterArg,
groupCount, newGroupCount));
}
relBuilder.aggregate(
relBuilder.groupKey(newGroupSet, false, null),
newAggCalls);//创建删除了GroupBy常量的汇总aggregate
AggregateCall:在Aggregate聚合操作中聚合方法的调用
adaptTo()方法:创建一个等效的AggregateCall,它适用于新的输入类型和/或GROUP BY中的列数。
遍历aggregate引用的所有字段列表(包括聚合方法内的字段),如果是聚合方法表达式,名称和位置不变,如果是常量则直接提取出常量值,如'F' 作为字段值放置到Project中。其他依次递增放置到以添加到Pair<字段表达式,字段名称>列表中。
// Create a projection back again.
List<Pair<RexNode, String>> projects = new ArrayList<>();
int source = 0;
for (RelDataTypeField field : aggregate.getRowType().getFieldList()) {//遍历聚合的字段列表
RexNode expr;
final int i = field.getIndex();
if (i >= groupCount) { //聚合中的使用字段,不是GroupBy中的字段,则名称和位置不变
// Aggregate expressions' names and positions are unchanged.
expr = relBuilder.field(i - map.size());
} else if (map.containsKey(i)) {//如果此字段 是常量字段,则把此字段放置到Project中。
expr = map.get(i);//从常量映射中,取出常量值,放置到Project中
} else {
expr = relBuilder.field(source);//否则,依次创建聚合表达式
++source;
}
projects.add(Pair.of(expr, field.getName()));//添加到Pair<字段表达式,字段名称>
}
relBuilder.project(Pair.left(projects), Pair.right(projects)); // 以字段名称,字段表达式创建Project投影操作
call.transformTo(relBuilder.build());
代码最后部分,以<字段名称,字段表达式>创建Project投影操作,做等价变换注册到RelSet等价的关系表达式集合,已备优化器选择。
总结
由于笔者知识及水平有限,因此文中错漏之处在所难免,恳请各位老师、专家不吝赐教。
往期文章分享
优化规则系列
Hive优化器原理与源码解析系列--优化规则SortRemoveRule(一)
Hive优化器原理与源码解析系列--优化规则SortJoinReduceRule(二)
Hive优化器原理与源码解析系列--优化规则SortProjectTransposeRule(三)
Hive优化器原理与源码解析系列--优化规则SortUnionReduceRule(四)
Hive优化器原理与源码解析系列--优化规则SortMergeRule(五)
Hive优化器原理与源码解析系列--优化规则ProjectFilterPullUpConstantsRule(六)
Hive优化器原理与源码解析系列--优化规则SortLimitPullUpConstantsRule(七)
Hive优化器原理与源码解析系列--优化规则UnionPullUpConstantsRule(八)
Hive优化器原理与源码解析系列--优化规则ProjectOverIntersectRemoveRule(九)
Hive优化器原理与源码解析系列--优化规则ProjectSortTransposeRule(十)
Hive优化器原理与源码解析系列--优化规则HiveProjectMergeRule(十一)
Hive优化器原理与源码解析系列--优化规则HiveJoinAddNotNullRule(十二)
Hive优化器原理与源码解析系列--优化规则HiveJoinCommuteRule(十三)
Hive优化器原理与源码解析系列--优化规则PartitionPruneRule(十四)
Hive优化器原理与源码解析系列--优化规则HivePreFilteringRule(十五)
Hive优化器原理与源码解析系列--优化规则HiveAggregateProjectMergeRule(十六)
成本模型系列
Hive优化器原理与源码解析系列—统计信息带谓词选择率Selectivity
Hive优化器原理与源码解析系列--统计信息中间结果大小计算
Hive优化器原理与源码解析系列—CBO成本模型CostModel(一)
Hive优化器原理与源码解析系列—CBO成本模型CostModel(二)
Hive优化器原理与源码解析系列—统计信息UniqueKeys列集合
Hive优化器原理与源码解析—统计信息Parallelism并行度计算