PostgreSQL数据库查询优化——查询树


  查询优化是数据库管理系统中承上启下的一个模块,它接收来自语法分析模块传递过来的查询树,在这个查询树的基础上进行了逻辑上的等价变换、物理执行路径的筛选,并且把选择出的最优的执行路径传递给数据库的执行器模块。查询优化器的输入是查询树,输出是查询执行计划。

  • 查询优化器和数据库用户之间的信息不对称,查询优化器在优化的过程中会参考数据库统计模块自动产生的统计信息,这些统计信息从各个角度来描述数据的分布情况,查询优化器会综合考虑统计信息中的各种数据从而得到一个好的执行方案,而数据库用户一方面无法全面地了解数据的分布情况,另一方面即使数据库用户获得了所有的统计数据,人脑也很难构建一个精确的代价计算模型来对执行方案进行筛选。
  • 查询优化器和数据库用户之间的时效性不同,数据库中的数据瞬息万变,一个在A时间点执行性能很高的执行计划,在B时间点由于数据内容发生了变化,它的性能可能就很低,查询优化器则随时都能根据数据的变化调整执行计划,而数据库用户则只能手动更改执行方案,和查询优化器相比,它的时效性比较低。
  • 查询优化器和数据库用户之间的计算能力不同,目前计算机的计算能力已经大幅提高,在执行数值计算方面和人脑相比具有巨大的优势,查询优化器对一个语句进行优化时,可以从几百种执行方案选出一个最优的方案,而人脑要全面地计算这几百种方案,需要的时间远远要长于计算机。

 

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

 文件介绍

  PostgreSQL数据库的查询优化的代码在src/backend/optimizer目录下,其中有plan、prep、path、geqo、util共5个子目录,plan是总入口目录,它调用了prep目录进行逻辑优化调用path、geqo目录进行物理优化,util目录是一些公共函数,供所有目录使用。在执行中,从Plan模块入口,先调用Prep模块进行预处理,再调用Path模块进行优化。Path模块中有开关,指示是否启用遗传算法进行优化,如果启用,且连接的表超过11,就调用geqo目录中的遗传算法进行优化。

  prep目录主要处理逻辑优化中的逻辑重写的部分,对投影、选择条件、集合操作、连接操作都进行了重写。

 

 path目录则主要是生成物理路径的部分,包括生成扫描路径、连接路径等。

 

 geqo目录主要是实现了一种物理路径的搜索算法——遗传算法,通过这种算法可以处理参与连接的表比较多的情况。

查询树

  PostgreSQL数据库中的结构体采用了统一的形式,它们都是基于Node结构体进行的“扩展”,Node结构体中只包含一个NodeTag成员变量,NodeTag是enum(枚举)类型

1 typedef struct Node{
2    NodeTag type;      
3 } Node;

其他的结构体则利用C语言的特性对Node结构体进行扩展,所有结构体的第一个成员变量也是NodeTag枚举类型,例如在List结构体里,第一个成员变量是NodeTag,它可能的值是T_List、T_intList或T_OidList,这样就能分别指代不同类型的List。

1 typedef struct List{
2    NodeTag type; // T_List, T_IntList, T_OidList
3    int length;
4    ListCell *head;
5    ListCell *tail;
6 } List;

  Query结构体以NodeTag枚举类型作为第一个变量,它的取值为T_Query

1 typedef struct Query{
2    NodeTag type;
3    CmdType commandType; // select | insert | update | delete | utility
4    QuerySource querySource; // where did I come from?
5    ......
6 } Query;

无论是List结构体的指针,还是Query结构体的指针,都能通过Node结构体的指针(Node*)来表示,而在使用对应的结构体时,则通过查看Node类型的指针中的NodeTag枚举类型就可以区分出该Node指针所代表的结构体的实际类型。

 

Query结构体

  Query结构体是查询优化模块的输入参数,其源自于语法分析模块,一个SQL语句在执行过程中,经过词法分析、语法分析和语义分析之后,会生成一颗查询树,PostgreSQL用Query结构体来表示查询树。查询优化模块在获取到查询树之后,开始对查询树进行逻辑优化,也就是对查询树进行等价变换,将其重写成一棵新的查询树,这个新的查询树又作为物理优化的输入参数,进行物理优化。

 

commandType:查询树对应命令的类型,它是一个枚举类型, 说明由哪类命令生成该查询树,包括以下几类:CMD_SELECT、CMD_INSERT、CMD_UPDATE、CMD_DELETE和CMD_UTILITY。如果命令类型为CMD_UTILITY,则查询优化器不会对该查询树进行优化。

querySource:是原始查询还是来自于规则的查询(被规则取代)

canSetTag:查询重写时用到,如果该Query是由原始查询转换而来则此字段为假,如果Query是由查询重写或查询规划时新增加的则此字段为真

resultRelation:结果关系,是涉及数据修改的范围表(实际使用的是范围表的编号)。

 List *rtable --> SQL是语句涉及的表清单,在查询中FROM子句后面会指出需要进行查询的范围表,可能是对单个范围表进行查询,也可能是对几个范围表做连接操作,rtable中则记录了这些范围表。rtable是一个List指针,所有要查询的范围表就记录在这个List中,每个表以RangeTblEntry结构体来表示,因此rtable是一个以RangeTblEntry结构体来表示,因此rtable是一个以RangeTbleEntry为节点的List链表。这个字段只适用于INSERT/UPDATE/DELETE命令,表示这些命令中需要更改的表或视图。

 targetList -->目标属性,用于存放查询结果属性的表达式。目标属性里的每个元素都包含一个表达式。它可以为常量值、某个范围表的一个属性、参数或者由函数调用、常量、变量、操作符等构成的表达式树。分以下四种情况:SELECT语句目标属性是位于SELECT和FROM之间的表达式;DELETE语句不需要目标属性,因为DELETE语句不返回任何元组;INSERT语句目标属性描述插入到结果关系的元组的属性,这些属性包括表名后指定的要插入值的属性或INSERT...SELECT语句中SELECT子句里的表达式,查询重写过程的第一步就是为查询中缺省字段增加目标属性,剩下的字段(既无指定值也无缺省值)将会赋予常量NULL;UPDATE目标属性描述被更新的属性,即SET子句中的属性。

jointree -->连接树,查询的连接树显示了FROM子句中表的连接情况。对于类似于SELECT...FROM a,b,c的简单查询,连接树只是FROM中表的简单列表,因为允许以任意顺序连接这些表。如果使用JOIN表达式(尤其是外连接),必须按照该连接指定的顺序进行连接。此时,连接树显示了JOIN表达式的结构。JOIN子句上的约束条件(ON或USING表达式)作为附加在连接树节点上的条件表达式处理。通常,还会把顶层WHERE表达式作为附加在顶层连接树节点上的条件表达式来处理,因此,连接树实际上表示了SELECT语句中的FROM和WHERE子句。 

 

JoinExpr结构体 

 

RangeTblEntry结构体

 

TargetEntry结构体

 

VAR结构体

  Var结构体表示查询中涉及的表的列属性,在SQL语句中,投影的列属性、约束条件中的列属性都是通过Var来表示的,在语法分析阶段会将列属性用ColumnRef结构体来表示,在语义分析阶段会将语法树中的ColumnRef替换成Var用来表示一个列属性。varno用来确定列属性所在的表的“编号”,这个编号源自Query(查询树)中的rtable成员变量,查询语句中涉及的每个表都会记录在rtable中,查询


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM