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

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

作者头像
用户7600169
发布2022-04-25 15:39:03
1.4K0
发布2022-04-25 15:39:03
举报
文章被收录于专栏:BigDataplusBigDataplus

目录

背景

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

代码语言: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优化规则的调用。

首先call.rel(0)获取Aggregate操作对象,并取得groupBy引用字段的个数,如果只有GroupBy只有一个字段,已经没有优化的空间,不可能把一个非空groupby转换为空groupBy,即不可能移除仅有一个常量字段。

代码语言:javascript
复制
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后没引用常量字段或引用常量字段没有在等值常量谓词中出现,则推出优化。

代码语言:javascript
复制
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对象。

代码语言:javascript
复制
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中的列数。

将上面GroupBy中移除后的常量,放置在新创建的Project投影。

遍历aggregate引用的所有字段列表(包括聚合方法内的字段),如果是聚合方法表达式,名称和位置不变,如果是常量则直接提取出常量值,如'F' 作为字段值放置到Project中。其他依次递增放置到以添加到Pair<字段表达式,字段名称>列表中。

代码语言:javascript
复制
// 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等价的关系表达式集合,已备优化器选择。

总结

优化规则AggregateProjectPullUpConstantsRule将等值谓词常量中出现的,并在GroupBy中引用的字段进行删除,为了保证其等价变换再上拉到Project投影中,减少中间分组计算的过程。

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

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

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

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

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