查询优化是数据库管理系统中承上启下的一个模块,它接收来自语法分析模块传递过来的查询树,在这个查询树的基础上进行了逻辑上的等价变换、物理执行路径的筛选,并且把选择出的最优的执行路径传递给数据库的执行器模块。查询优化器的输入是查询树,输出是查询执行计划。
查询优化器和数据库用户之间的信息不对称,查询优化器在优化的过程中会参考数据库统计模块自动产生的统计信息,这些统计信息从各个角度来描述数据的分布情况,查询优化器会综合考虑统计信息中的各种数据从而得到一个好的执行方案,而数据库用户一方面无法全面地了解数据的分布情况,另一方面即使数据库用户获得了所有的统计数据,人脑也很难构建一个精确的代价计算模型来对执行方案进行筛选。
查询优化器和数据库用户之间的时效性不同,数据库中的数据瞬息万变,一个在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 structNode{2 NodeTag type;3 } Node;
其他的结构体则利用C语言的特性对Node结构体进行扩展,所有结构体的第一个成员变量也是NodeTag枚举类型,例如在List结构体里,第一个成员变量是NodeTag,它可能的值是T_List、T_intList或T_OidList,这样就能分别指代不同类型的List。
1 typedef structList{2 NodeTag type; //T_List, T_IntList, T_OidList
3 intlength;4 ListCell *head;5 ListCell *tail;6 } List;
Query结构体以NodeTag枚举类型作为第一个变量,它的取值为T_Query
1 typedef structQuery{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结构体来表示查询树。查询优化模块在获取到查询树之后,开始对查询树进行逻辑优化,也就是对查询树进行等价变换,将其重写成一棵新的查询树,这个新的查询树又作为物理优化的输入参数,进行物理优化。
List *rtable --> SQL是语句涉及的表清单,在查询中FROM子句后面会指出需要进行查询的范围表,可能是对单个范围表进行查询,也可能是对几个范围表做连接操作,rtable中则记录了这些范围表。rtable是一个List指针,所有要查询的范围表就记录在这个List中,每个表以RangeTblEntry结构体来表示,因此rtable是一个以RangeTblEntry结构体来表示,因此rtable是一个以RangeTbleEntry为节点的List链表。
Var结构体表示查询中涉及的表的列属性,在SQL语句中,投影的列属性、约束条件中的列属性都是通过Var来表示的,在语法分析阶段会将列属性用ColumnRef结构体来表示,在语义分析阶段会将语法树中的ColumnRef替换成Var用来表示一个列属性。varno用来确定列属性所在的表的“编号”,这个编号源自Query(查询树)中的rtable成员变量,查询语句中涉及的每个表都会记录在rtable中,查询