openGauss学习——optimizer 查询优化概述

引言

在之前的博客中我主要对openGauss SQL引擎中的查询解析parser和查询重写rewriter这两个模块进行了学习和相关源文件的解析。我将openGauss SQL引擎中的几个主要模块parser、rewriter、optimizer、executor之间的关联关系、输入输出以及与数据库基本表之间的交互关系总结如下图。在之后我会开展对查询优化optimizer模块的学习。这也将是在本次比赛中我个人负责的最后一个模块的学习。

基于代价的查询优化

在前面的学习中我们已经知道,数据库的查询优化方法分为两个层次:基于规则的查询优化(逻辑优化,Rule Based Optimization,简称RBO)和基于代价的查询优化(物理优化,Cost Based Optimization,简称CBO)。

其中RBO是建立在关系代数基础上的优化,关系代数中有一些等价的逻辑变换规则,通过对关系代数表达式进行逻辑上的等价变换,可能会获得执行性能比较好的等式,这样就能提高查询的性能。而CBO则是在建立物理执行路径的过程中进行优化。例如,关系代数中虽然指定了两个关系的连接操作,但是这时的连接操作符属于逻辑运算符,它没有指定以何种方式实现这种逻辑连接操作,而查询执行器executor是不认识关系代数中的逻辑连接操作的,我们需要生成多个物理连接路径来实现关系代数中的逻辑连接操作,并根据查询执行器的执行步骤,建立代价计算模式,通过计算所有的物理连接路径的代价,从中选择出最优的路径

我们知道,查询重写rewriter是基于优化规则的等价变换,属于逻辑优化,也可以称为基于规则的优化。那么,怎么衡量对一个SQL语句进行查询重写之后,它的性能一定是提升的呢?如上面所说,此时基于代价对查询重写进行评估就非常重要了,因此查询重写不只是基于规则的查询重写,还可以是基于代价的查询重写

查询优化的大致阶段

在查询解析工作完成之后,其最终产物—查询树链表将被移交给查询优化模块,该模块的入口函数是pg _plan_queries,它负责将查询树链表变成执行计划链表。pg_plan_queries 只会处理非Utility命令( 即 SELECT/INSERT/UPDATE/DELETE/MERGE 指令,详见 SQL rewriter查询重写概述 ),它调用pg_plan_query对每一个查询树进行处理,并将生成的 PlannedStmt结构体构成一个链表(执行计划链表)返回。pg_plan_query中负责实际计划生成的是planner函数,它也是optimizer的入口函数。

PlannedStmt* planner(Query* parse, int cursorOptions, ParamListInfo boundParams)

如上所述,查询优化的最终目的是得到可被执行模块执行的最优计划,其整个过程可分为预处理、生成路径和生成计划三个阶段。

  1. 预处理阶段。预处理实际上是对查询树(Query结构体)的进一步改造,这种改造可通过SQL语句体现。在此过程中,最重要的是提升子链接和提升子查询;

  2. 生成路径阶段。接收到重写后的查询树后,采用动态规划算法或遗传算法,生成最优连接路径和候选路径链表;

  3. 生成计划阶段。用得到的最优路径,首先生成基本计划树,然后添加GROUP BY、HAVING和ORDER BY等子句所对应的计划节点形成完整计划树。

optimizer源码组织

openGauss查询优化模块大概由以下几部分组成(各部分文件路径见上图):

1. 查询重写prep。注意此重写非彼重写(rewriter),rewriter主要负责对查询树应用既定义的重写规则,而此处的查询重写主要负责子查询优化、谓词化简及正则化、谓词传递闭包等查询优化预处理阶段的重写工作。

2. 统计信息。它负责生成各种类型的统计信息,供代价估算模块来使用。

3. 代价估算。它主要进行选择率估算、行数估算、代价估算等,基于代价对查询重写进行评估。

4. 物理路径path。它负责生成执行的物理路径,包括最优连接路径和候选路径链表等。

5. 动态规划plan。它负责通过动态规划方法对物理路径进行搜索。

6. 遗传算法geqo。它负责通过遗传算法对物理路径进行搜索。需要注意的是,指示是否启用遗传算法进行优化的开关在path模块中;如果启用,且连接的表超过一定数量,就调用geqo目录中的遗传算法进行物理路径的优化。

入口函数 planner()

函数源码及部分注释如下:

/*****************************************************************************
 *
 *       Query optimizer entry point
 *
 * To support loadable plugins that monitor or modify planner behavior,
 * we provide a hook variable that lets a plugin get control before and
 * after the standard planning process.  The plugin would normally
 * call standard_planner().
 *
 *****************************************************************************/
PlannedStmt* planner(Query* parse, int cursorOptions, ParamListInfo boundParams)
{
    PlannedStmt* result = NULL;
    instr_time starttime;
    double totaltime = 0;
    INSTR_TIME_SET_CURRENT(starttime);
#ifdef PGXC
    ……
    /*
     * A Coordinator receiving a query from another Coordinator
     * is not allowed to go into PGXC planner.
     */
    if ((IS_PGXC_COORDINATOR || IS_SINGLE_NODE) && !IsConnFromCoord())
        result = pgxc_planner(parse, cursorOptions, boundParams);
    else
#endif
        result = standard_planner(parse, cursorOptions, boundParams);
    totaltime += elapsed_time(&starttime);
    result->plannertime = totaltime;
    ……
    return result;
}

阅读函数头部注释我们可以知道,为了支持可加载的插件来监视或修改规划者的行为,函数提供了一个hook变量,让插件在标准规划过程之前和之后获得控制权。并且通常情况下,插件会进入调用standard_planner() 的分支。

函数的调用逻辑还是较为简单的。在进行了必要的变量声明和初始化后,进入到一个逻辑分支,这个分支会决定调用函数pgxc_planner还是standard_planner,而两者都会返回一个PlannedStmt类型的结构体给result。根据#ifdef PGXC和if语句内的宏名可以推测,pgxc_planner的调用与分布式的PG版本PGXC有关,因此大多数时候都是调用standard_planner函数来获取PlannedStmt结构体,即优化后的查询树。并且在分支的前后,函数通过INSTR_TIME_SET_CURRENT宏和函数elapsed_time来统计planner的执行时间,更新到reslut的字段plannertime中。

/* typedef struct PlannedStmt */
double plannertime; /* planner execute time */

结构体PlannedStmt定义的部分源码如下,它位于文件plannode.h中。

typedef struct PlannedStmt {
    NodeTag type;
    CmdType commandType; /* select|insert|update|delete */
    ……
    Plan* planTree; /* tree of Plan nodes */
    List* rtable; /* list of RangeTblEntry nodes */
    /* rtable indexes of target relations for INSERT/UPDATE/DELETE */
    List* resultRelations; /* integer list of RT indexes, or NIL */
    Node* utilityStmt; /* non-null if this is DECLARE CURSOR */
    List* subplans; /* Plan trees for SubPlan expressions */
    ……
    double plannertime; /* planner execute time */
    ……
} PlannedStmt;

总结

在本篇博客中我对openGauss查询优化optimizer模块进行了简要概述。在之后的博客中我会开展对这一部分相关源文件的学习和解读工作。感谢阅读。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值