zoukankan      html  css  js  c++  java
  • 基于calcite做傻瓜式的sql优化(三)

     上一篇:基于calcite做傻瓜式的sql优化(二)

    上一篇说到的是Hive是如何对sql进行解析,生成ASTNode 

    那么Hive拿到ASTNode之后,就会触发:BaseSemanticAnalyzer.analyze这个方法;

    这个方法非常的重要,从AST到task的生成这一系列的操作,都会在这个调用栈下进行的;

     如下图:

     按照:基于calcite做傻瓜式的sql优化给出的sql示例,我们提前看下,经过hive各阶段优化后,会改变什么样子

    sql:

    select 
      * 
    from 
      (
        select 
          Sname, 
          Sex, 
          Sage, 
          Sdept, 
          count(1) as num 
        from 
          student_ext 
        group by 
          Sname, 
          Sex, 
          Sage, 
          Sdept
      ) t1 
      left join student_ext t2 on t1.Sname = t2.Sname 
    where 
      t1.Sage > 10 
      and t2.Sdept = 'MA';
    ##########################Gen Calcite Plan##############################################
    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################applyPreJoinOrderingTransforms-0##############################################
    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################Push Down Semi Joins##############################################
    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################JOIN Add not null filters##############################################
    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################Constant propagation, common filter extraction, and PPD##############################################
    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################basePlan##############################################
    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################Projection Pruning##############################################
    HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
      HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
        HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
          HiveProject($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[1])
            HiveProject(sname=[$1], sex=[$2], sage=[$3], sdept=[$4])
              HiveTableScan(table=[[default.student_ext]])
        HiveProject(sno=[$0], sname=[$1], sex=[$2], sage=[$3], sdept=[$4])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################Apply Pre Join Order optimizations##############################################
    HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
      HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
        HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
          HiveProject($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[1])
            HiveProject(sname=[$1], sex=[$2], sage=[$3], sdept=[$4])
              HiveTableScan(table=[[default.student_ext]])
        HiveProject(sno=[$0], sname=[$1], sex=[$2], sage=[$3], sdept=[$4])
          HiveTableScan(table=[[default.student_ext]])
    
    ##########################优化后的执行计划##############################################
    HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
      HiveJoin(condition=[=($0, $6)], joinType=[left], algorithm=[none], cost=[not available])
        HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
          HiveProject($f0=[$0], $f1=[$1], $f2=[$2], $f3=[$3], $f4=[1])
            HiveProject(sname=[$1], sex=[$2], sage=[$3], sdept=[$4])
              HiveTableScan(table=[[default.student_ext]])
        HiveProject(sno=[$0], sname=[$1], sex=[$2], sage=[$3], sdept=[$4])
          HiveTableScan(table=[[default.student_ext]])
    View Code

    上面就是在各阶段优化,产生的执行计划

    • 1. 生成Calcite的执行计划:RelNode

    持续跟进,最终会调用到一个抽象方法:

    public abstract void analyzeInternal(ASTNode ast) throws SemanticException;

     上文提到过,在Hive中,使用Calcite来进行核心优化,它将AST Node转换成QB,又将QB转换成Calcite的RelNode,在Calcite优化完成后,又会将RelNode转换成Operator Tree,说起来很简单,但这又是一条很长的调用链。

    简答来说:Hive是基于antlr做的词法和语法解析后生成的语法树,然后基于Calcite对语法树做深度优先遍历,在遍历过程中通过匹配规则来剪掉部分Operator或者合并Operattor等,这样就大大减小了shuffle数据量(其实就是RBO和CBO);

    因此程序走到这个抽象方法后,就会跳到hive的优化实现类:CalcitePlanner类上

     这样程序就进入:CalcitePlanner.analyzeInternal; 然后判断,是否需要进行CBO优化;

     当然不管执行的是CBO还是RBO,其实最终走的都是:analyzeInternal(ASTNode ast, PlannerContext plannerCtx),如下图:

    接下来就会走到非常重要的代码:

    //TODO  2. Gen OP Tree from resolved Parse Tree rbo优化的地方
    Operator sinkOp = genOPTree(ast, plannerCtx);

    上面这段代码,就会基于Calcite对ast进行各种规则的优化,然后返回Operator

    所以跟进genOPTree方法:

    • 2. 对Join操作进行规则优化

     直接跳到他的实现方法上:CalcitePlanner.genOPTree(ASTNode ast, PlannerContext plannerCtx)

     上面这段代码意思非常明确,是否需要进行CBO优化,如果不需要的话,会直接执行最下面的代码,返回未经优化的Operator

     if (skipCalcitePlan) {
          sinkOp = super.genOPTree(ast, plannerCtx);
        }

    如果需要进行CBO优化,代码既执行else内部的逻辑

    其中优化核心代码是:

    ASTNode newAST = getOptimizedAST();

     这段代码产生的hive优化流程:

    1. 生成Calcite的执行计划:RelNode
    2. 对Join操作进行规则优化
      2.1、聚合去重
      2.2、Semi Joins的下推
      2.3、Add not null filters
      2.4、Join的谓词下推,投影提取、常量合并等工作
      2.5、谓词推送到下游并进行分区修剪
      2.6、投影修剪
      2.7、列剪枝       
    3. Appy Join Order Optimizations using Hep Planner (MST Algorithm)

    那么接下来看下,hive是如何完成上述操作的:

    Calcite优化的主要类是CalcitePlanner,更加细节点,是在CalcitePlannerAction.apply()这个方法,如下图:

     进入CalcitePlannerAction这个内部类,优化的重点就在CalcitePlannerAction.apply()这个方法:

    这个apply方法由三个重要的局部变量:

    //calcite基于QB生成一个初始化的RelNode
    RelNode calciteGenPlan = null;
    //执行CBO优化后生成的RelNode
    RelNode calcitePreCboPlan = null;
    //经过一些列规则优化之后,返回的结果RelNode
    RelNode calciteOptimizedPlan = null;

     1、calciteGenPlan

    hive会根据事先生成好的QB,来转化为初始化的RelNode,而calcite对sql优化,其实就是针对RelNode进行优化

    其中代码:

    calciteGenPlan = genLogicalPlan(getQB(), true);

    就是QB中获取成员变量的值,然后将这些值重组成RelNode(类似)

     这样当代码拿到基于QB重组成的RelNode之后(calciteGenPlan),然后就开始进行CBO的规则优化

    • 2.1、聚合去重

    程序进入applyPreJoinOrderingTransforms方法后:首先做一个计算引擎的判断:

    如果当前支持tez计算引擎,并且支持优化去重重写操作,那么Hive会进行一次HiveExpandDistinctAggregatesRule的优化:

    HiveExpandDistinctAggregatesRule

    那么问题来了,HiveExpandDistinctAggregatesRule是什么优化?

    这种优化简单来说就是将:count dintict进行扩展为聚合的优化方式

    听起来很别扭是吧?

    举个栗子:

    count(distinct colA)就是将colA中所有出现过的不同值取出来,相信只要接触过数据库的同学都能明白什么意思!

    count(distinct colA)的操作也可以用group by的方式完成,具体代码如下:

    select count(distinct colA) from table1;
    select count(1) from (select colA from table1 group by colA)alias_1;

    这两者最后得出的结果是一致的! , 但是具体的实现方式,有什么不同呢?

    上面两种方式本质就是时间与空间的权衡。
    distinct需要将colA中的所有内容都加载到内存中,大致可以理解为一个hash结构,key自然就是colA的所有值。因为是hash结构,那运算速度自然就快。最后计算hash中有多少key就是最终的结果。
    那么问题来了,在现在的海量数据环境下,需要将所有不同的值都存起来,这个内存消耗,是可想而知的。所以如果数据量特别大,可能会out of memory。。。

    group by的实现方式是先将colA排序。排序大家都不陌生,拿最见得快排来说,时间复杂度为O(nlogn),而空间复杂度只有O(1)。这样一来,即使数据量再大一些,group by基本也能hold住。但是因为需要做一次O(nlogn) 的排序,时间自然会稍微慢点

    虽然时间慢了,但是在海量数据,比如:10T大小的表情况下,相比count dintict肯定优先选择group by

    • 2.2、Semi Joins的下推

    接下来就会走到常规的第一次优化,代码如下:

    // 1. Push Down Semi Joins
          basePlan = hepPlan(basePlan, true, mdProvider, SemiJoinJoinTransposeRule.INSTANCE,
            SemiJoinFilterTransposeRule.INSTANCE, SemiJoinProjectTransposeRule.INSTANCE);

    这里面涉及到的优化规则是:

    SemiJoinJoinTransposeRule
    SemiJoinFilterTransposeRule
    SemiJoinProjectTransposeRule

    这三个规则都是关于SemiJoin的优化

    简单介绍下semi join的作用:

    常规联接中,结果可能会出现重复值,而子查询可以获得无重复的结果。

    比如需要找出有人口大于 2000万的城市的国家,如果用普通联接,则可能出现重复结果:

    select country.* from country join city on country.code=city.country_code 
    and population>20000000;
    +---------+----------+
    | code    | name     |
    +---------+----------+
    |    1    | china    | 
    |    1    | china    |
    +---------+----------+
    2 rows in set (0.00 sec)

    出现这种情况,一般会使用子查询来解决,比如:

    select * from country where code in (select country_code from city where population>20000000);
    +------+---------+
    | code | name    |
    +------+---------+
    |  1   | china   |
    +------+---------+
    1 row in set (0.00 sec)

    但是,仔细观察sql会发现,这种子查询的性能很糟糕,因为where后面的子查询每扫描一条数据,Where子查询都会被重新执行一遍,这样效率就会很低如果父表数据很多带来什么问题?那么就有了将子查询的结果提升到FROM中,不需要再父表中每个符合条件的数据都要去把子查询执行一轮了;所以为了完成同样目标,我们可以选择semi join来做优化。

    比如:

    select country.* from country semi join city on country.code = city.country_code where population > 20000000;

    现在我们在拿这个例子semi join来做优化,经过:SemiJoinJoinTransposeRule,SemiJoinFilterTransposeRule,SemiJoinProjectTransposeRule这些规则处理后

    最后生成的sql就是:

    select country.* from counttry semi join (select country_code from city where population > 20000000) a on country.code = a.country_code

    经过Push Down Semi Joins流程后,代码接下来执行到如图所示的地方:

    • 2.3、Add not null filters

     这里面涉及到一个规则:HiveJoinAddNotNullRule

    此优化规则Rule主要功能是将SQL语句中Inner Join关联时,出现在关联条件中的字段存在为null可能的字段,都加上相应字段 is not null条件限制(因为hive在做关联的时候,并不会对null = null这样的条件进行关联)

    贴一个连接,将HiveJoinAddNotNullRule讲的非常透彻,建议深入看一下:连接

    比如执行这样一个sql:

    select * from (select Sname , Sex , Sage , Sdept , count(1) as num from student_ext group by Sname , Sex , Sage , Sdept) t1  inner join student_ext t2 on t1.Sname = t2.Sname where t1.Sage>10 and t2.Sdept = 'MA';

    在HiveJoinAddNotNullRule规则优化前后对比的执行计划如下:

    在经过HiveJoinAddNotNullRule优化

    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[inner], algorithm=[none], cost=[not available])
          HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
            HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
              HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                HiveTableScan(table=[[default.student_ext]])
          HiveTableScan(table=[[default.student_ext]])

    在经过HiveJoinAddNotNullRule优化

    HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4], sno=[$5], sname1=[$6], sex1=[$7], sage1=[$8], sdept1=[$9])
      HiveFilter(condition=[AND(>($2, 10), =($9, 'MA'))])
        HiveJoin(condition=[=($0, $6)], joinType=[inner], algorithm=[none], cost=[not available])
          HiveFilter(condition=[isnotnull($0)])
            HiveProject(sname=[$0], sex=[$1], sage=[$2], sdept=[$3], num=[$4])
              HiveAggregate(group=[{0, 1, 2, 3}], agg#0=[count($4)])
                HiveProject($f0=[$1], $f1=[$2], $f2=[$3], $f3=[$4], $f4=[1])
                  HiveTableScan(table=[[default.student_ext]])
          HiveFilter(condition=[isnotnull($1)])
            HiveTableScan(table=[[default.student_ext]])

    仔细观察,多了 HiveFilter(condition=[isnotnull($1)]) ,这个就是HiveJoinAddNotNullRule规则的作用,出现在关联条件中的字段存在为null可能的字段,都加上相应字段 is not null条件限制

    2.4、Constant propagation, common filter extraction, and PPD

    继续跟进debug,程序走到 3. Constant propagation, common filter extraction, and PPD

    第一个规则:ReduceExpressionsRule.PROJECT_INSTANCE 叫做常量折叠 , 比如我们写这样一个sql:

    select  1+2 , a.name , a.age ,b.money from a left join b on a.id=b.id where a.name='张三' and b.department='it'

    在没有进行常量折叠优化之前,如果不进行常量折叠,那么每行数据都需要进行计算,显然会增大sql的CPU使用情况

    然后是下面的三个规则要放在一起:

    ReduceExpressionsRule.FILTER_INSTANCE,
    ReduceExpressionsRule.JOIN_INSTANCE
    HivePreFilteringRule.INSTANCE

    就是在join的过程中帮我们进行谓词下推操作;

    那么Constant propagation, common filter extraction, and PPD这个优化规则组合起来,用一张图来说明一下,依然是sql:

    select  1+2 , a.name , a.age ,b.money from a left join b on a.id=b.id where a.name='张三' and b.department='it'

    图:

     从上图的优化前后对比可以看到,当需要查询的表数据量很大是后续,这种优化能极大优化:

    1、join过程中的数据量
    2、CPU计算次数

    未完待续.............

  • 相关阅读:
    资料收集
    layui 设计资源——2.0 版本的 Axure 组件包,产品交互设计利器
    照片自动按时间分类助手
    回顾2017,规划2018,展望2019
    coder/programmer engineer Chirf Technology Offcer
    新手开公司创业,这些坑千万别掉进去!
    ASP.NET MVC中的Global.asax文件
    SQLServer(MSSQL)、MySQL、SQLite、Access相互迁移转换工具 DB2DB v1.0
    快速开发之代码生成器(asp.net mvc4 + easyui + knockoutjs)
    PowerDesigner逆向工程导入MYSQL数据库总结
  • 原文地址:https://www.cnblogs.com/niutao/p/14027732.html
Copyright © 2011-2022 走看看