文章目录

  • ​​一、创建RelOptInfo​​
  • ​​1.RelOptInfo结构体​​
  • ​​2.IndexOptInfo结构体​​
  • ​​3.创建RelOptInfo​​
  • ​​二、初识等价类​​
  • ​​三、谓词下推​​
  • ​​四、PlaceHolderVar的作用​​
  • ​​五、Lateral语法的支持​​
  • ​​1.Lateral的语义分析​​
  • ​​2.收集Lateral变量​​
  • ​​3.收集Lateral信息​​
  • ​​六、消除无用连接项​​
  • ​​七、Semi Join消除​​
  • ​​八、提取新的约束条件​​
  • ​​1.提取需要满足的条件​​
  • ​​2.提取流程​​
  • ​​3.选择率修正​​

一、创建RelOptInfo

在进行逻辑重写优化的过程中,查询树中的连接树Query->jointree是通过RangeTblEntry组织起来的

  • 在逻辑分解优化的过程中,所有的RangeTblEntry将建立一个对应的RelOptInfo结构体,这是因为查询树Query中的RangeTblEntry结构体在物理优化时已经不适用了,必须要使用适用于物理优化的RelOptinfo结构体来代替他
  • RelOptInfo结构体对应的是查询语句中的范围表,它是对一个表进行描述,属于逻辑层面,它的内部没有提供和物理代价以及物理路径相关的成员变量
  • RelOptInfo结构体在设计的时候更多地考虑了生成物理连接路径以及计算路径代价,属于物理层
  • 在查询树中约束条件就是一个表达式,此时的表达式是一个裸表达式,即它只是保存了表达式本身所需的内容;
    在逻辑分解阶段,会将这些裸表达式用RestrictInfo结构体来进行封装,目的是用于扩展表达式的内容,在RestrictInfo结构体中还记录了约束条件在物理优化过程中需要的变量。
  • 在查询树Query中,约束条件(表达式)存放的位置就是它原始的语法位置,在逻辑分解的过程中,会对这些约束条件下推。
    eg:在逻辑分解阶段会基于等价类进行推理,生成一些隐含的约束条件,eg:在满足一定条件的情况下,约束条件A=B和约束条件B=C能够推到出新的约束条件A=C

谓词下推、连接顺序交换、基于等价类的推理是查询优化的难点、也时重点;若没有理解逻辑分解优化,那么物理优化的部分理解也会很困难

1.RelOptInfo结构体

查询树中的基表信息以RangeTblEntry的形式存放在Query->rtable链表中,在物理优化阶段,因为针对每个基表都需要生成扫描路径Scan,多个基表之间还会产生连接Join路径,且需要计算这些路径的代价,所以需要一个新的结构体RelOptInfo来替换原来的RangeTblEntry。

RelOptInfo结构体
查询优化过程中,首先面对的是FROM子句中的表,通常称之为范围表RangeTable

  • 可能是一个常规意义上的表、也可能是一个子查询、或者是一个查询结果的组织为表状的函数(eg:TableFunction)
  • 这些表处于查询执行计划的叶子节点,是产生最终查询结果的基础,称之为基表,这些基表可以用RelOptInfo结构体表示,它的RelOptInfo->reloptkind是RELOPT_BASEREL
  • 基表之间可以进行连接,连接操作产生的中间结果也可以用RelOptInfo结构体来表示,其RelOpInfo->reloptkind=RELOPT_JOINREL

无论是RELOPT_BASEREL还是RELOPT_JOINREL,RelOptInfo结构体中的公共变量如下

<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_数据库


<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_约束条件_02


RELOPT_BASEREL基表类型必用的变量

<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_等价类_03


<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_等价类_04

2.IndexOptInfo结构体

每个IndexOptInfo结构体代表一个索引,若一个表有多个IndexOptInfo,则其结构体存储在RelOptInfo->indexlist中,这些索引主要用于生成索引扫描路径、计算索引扫描代价、获得索引扫描的结果是否具有时序性等

  • 索引可以分为唯一索引、主键索引、局部索引和表达式索引
  • 不同的索引,PG提供了不同的访问方法(Access Method),访问方法保存在PG_AM系统表中,amhandler是访问方法的初始化方法

IndexOptInfo结构体如下:

<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_database_05

3.创建RelOptInfo

PlannerInfo中有2个数组,分别是simple_rte_array和simple_rel_array,分别的负责记录RangeTblEntry和RelOptInfo

  • 在setup_simple_rel_arrays函数中,会将Query->rtable中的RangeTblEntry按顺序提取出来,记录到simple_ste_array中
  • 在add_base_rels_to_query函数中,会将Query->jointree中的RangeTblRef提取出来,按照simple_rte_array中记录的顺序,分别为每个RangeTblRef创建RelOptInfo,实际上也是为每个RangeTblEntry创建对应的RelOptInfo

Query->jointree中有三种类型的Node节点

  • FromExpr、JoinExpr和RangeTblRef
  • FromExpr和JoinExpr也是RangeTblRef结构体
  • 所以对Query->jointree做深度遍历,直到发现RangeTblRef节点,就创建对应的RelOptInfo结构体,并且将RelOptInfo放到对应的Query->_simple_rel_arrays数组中
  • 整体流程如下

    创建RelOptInfo的具体过程是在build_simple_rel函数中实现的,这里创建的是基表对应的RelOptInfo结构体,如下图所示,基表的主要类型是RELOPT_BASEREL和RELOPT_OTHER_MEMBER_REL两种类型,基表又可以根据RangeTblEntry->rtekind引申出更多类型,eg:普通表、子查询、VALUES子查询、函数型基表等
  • eg查询实体从逻辑层向物理层转换的类型对照

二、初识等价类

SQL语句中,会有A=B这样的约束条件,它的操作符是等值操作符,我们将这种等值约束条件称为等价条件,基于多个等价条件进行推理而获得的等价的属性的集合就是等价类。

  • eg:若SQL中一个约束条件为A=B,那么通过这个约束条件而产生的连接结果中A和B一定是相等的,若查询结果是按照A进行排序的,那么可以得知查询结果也一定是按照B排序
  • 等价类的数据结构EquivalenceClass如下
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_等价类_06


A=B的约束条件,它的操作符是等值操作符,我们将这种等值约束条件称之为等价条件

  • 基于多个等价条件进行推理而获得的等价的属性的集合是等价类
  • eg:下面的sql语句产生的3个等价类会保存在PlannerInfo->eq_classes
select * from student left join (score inner join course on score.cno=course.cno and course.cno=5) on student.sno=score.sno where student.sno=1

<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_约束条件_07

三、谓词下推

RelOptInfo结构体在生成之后是保存在数组中的,即这些基表已经从树状结构(Query->jointree)被拉平成线性结构(PlannerInfo->simple_rel_array),但是基表之间的逻辑关系无法在PlannerInfo->simple_rel_array中体现,所以逻辑分解优化要做的工作一方面要将约束条件下推到RelOptInfo,另一方面就是要将基表之间的连接类型记录起来。

连接条件的下推

  • eg:下面的sql中的连接条件student.sno=1在执行计划中被下推到了student表的扫描路径上了,成为了一个过滤条件,这样减少了student表扫描的结果数量,降低了连接操作的代价
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_database_08


过滤条件的下推

  • where语句后面的是过滤条件,left join后面的on是连接条件

连接顺序

  • 查询优化器在尝试生成连接路径的时候会尝试交换基表的之间的连接顺序,目的是生成更多的候选路径,但是连接顺序并不能随意交换
  • 在全连接的情况下,PG不允许交换连接顺序

deconstruct_recurse函数

  • 在逻辑分解优化的过程中,对Query->jointree递归处理后,后续的工作就由Query->simple_rel_array接手处理,并且填充已经在Query->simple_rel_array创建好的RelOptInfo
  • deconstruct_recurse函数对Query->jointree的递归遍历分为3部分,分别处理RangeTblRef、FromExpr和JoinExpr,且对FromExpr和JoinExpr做深度遍历,直到发现RangeTblRef叶节点
  • deconstruct_recurse函数中重要的变量如下:
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_数据库_09


  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_数据库_10


  • eg:约束条件的延迟处理
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_等价类_11


make_outerjoininfo函数

  • SpecialJoinInfo一方面记录在deconstruct_recurse函数中发现的外连接,另一方面对于不能进行连接顺序交换的表需要通过这个结构体来限制
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_postgresql_12


distribute_qual_to_rels函数

  • 参照约束条件下推(谓词下推)的规则对约束条件进行下推

  • 在之前的逻辑变换优化过程中,已经对约束条件进行了正则化,保证了约束条件的顶层形式是合取范式,deconstruct_recurse函数会分别针对合取范式中的每一个单独的子约束条件调用distribute_qual_to_rels进行处理
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_数据库_13


  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_database_14


  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_postgresql_15

  • 约束条件就是WHERE/ON/HAVING子句中的各个条件,在查询树中, 他们以表达式Expr的方式存在,主要存放在FromExpr的quals链表中和JoinExpr的quals链表中,这类条件通常称为约束条件;
    在分解连接树的过程中,这部分又分为过滤Filter条件和连接Join条件

  • 在逻辑重写优化的预处理表达式(preprocess_expression函数)的过程中对谓词进行了正则处理,PG默认约束条件顶层的形式符合合取范式(AND形式)。

  • 在逻辑分解优化的过程中,会将这些约束条件分发给RelOptInfo,同时会将原来的约束条件(表达式)转换成ResTrictInfo结构体,每个ResTrictInfo对应一个约束条件。
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_数据库_16


  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_约束条件_17

  • 添加Var到targetlist
    在约束条件中会涉及一些表上的列,在查询计划的执行阶段,这些列需要被求值,然后判断约束条件的真或者假,所以需要将这些列增加到RelOptInfo->reltarget中,另外,引用这个列属性的约束条件引用了哪些表,会方法到RelOptInfo->attr_needed

  • 生成等一个约束条件是否可以用于生成等价类取决于这个约束条件是否适用于Merge Join,即:这个约束条件必须是MergeJoinable的,distribute_qual_to_rels函数才会考虑将这个约束条件进行生成等价类的处理。

  • 分发约束条件给RelOptInfo
    有些约束条件RestrictInfo被记录到了等价类中,有些被记录到了PlannerInfo中,这些约束条件在目前的阶段不分发给RelOptInfo,但是对于其他生成单成员等价类的约束条件,需要对其进行分发,分发结果如下:
  • <postgreSQL查询优化深度探索>之第四章:逻辑分解优化_postgresql_18

reconsider_outer_join_clauses函数

  • 在生成等价类的阶段,有一些条件没有真正下推下去,而是下推到PlannerInfo中的left_join_clauses、right_join_clauses、full_join_clauses,这些条件没有下发下去是因为还有优化空间,所以还需reconsider_outer_join_clauses函数来做"revonsider"

generate_base_implied_equalities函数

  • 除了在PlannerInfo中的left_join_clauses、right_join_clauses、full_join_clauses中记录了外连接相关的不能下推的约束条件外,能够生成等价类的约束条件也没有下推,这些约束条件Restriction记录在了等价类的EquivalenceClass->ec_sources中,generate_base_implied_equalities函数可以在对其做优化

记录表之间的等价关系

  • 目前要做的是一个等价类中的成员涉及到了哪些表记录下来,这样在生成执行路径的时候,就可以根据这个表上有没有等价类来确定是不是可能根据等价类来生成连接条件,从而生成连接关系
四、PlaceHolderVar的作用

PlaceHolderVar就是一种特殊的Var,它是对Var功能的一种增强,在查询优化过程中需要对查询树进行基于逻辑的等价变换,变换的过程中需要调整子查询的位置、调整Var变量的位置或者消减掉一些不必炫耀的操作。

在有些情况下,逻辑优化会受到限制,eg:调整Var变量的位置之后查询树逻辑上就不等价了,此时需要找到一个方法,即能调整Var变量的位置,还要保证逻辑等价,此时PlaceHolderVar就诞生了。

PlaceHolderVar数据结构如下:

<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_约束条件_19


<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_postgresql_20

五、Lateral语法的支持

在query_planner函数中分别调用find_lateral_references函数和create_lateral_join_info函数来处理Lateral语法中涉及到的Var或者PlaceHolderVar

1.Lateral的语义分析

<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_postgresql_21


<postgreSQL查询优化深度探索>之第四章:逻辑分解优化_等价类_22

2.收集Lateral变量

对Lateral的列属性的处理主要集中在数据库的查询优化阶段

  • 在查询树中收集Lateral变量,find_lateral_references函数遍历PlannerInfo中的RelOptInfo数组(PlannerInfo->simple_rel_array),从这些RelOptInfo中查找是否有Lateral列属性

3.收集Lateral信息

create_lateral_join_info函数负责建立表之间的依赖关系

  • 该函数是在逻辑优化阶段最后一次对Lateral遍历进行处理,有了lateral_relids等变量,在代价优化阶段生成执行路径Path时,就可以根据这些变量判断表之间的连接熟悉怒,建立参数化路径
六、消除无用连接项

数据库在生成连接路径时,需要考虑所有的表之间的连接路径,若能消除其中的一些表,那么势必能降低物理路径的搜索空间,从而提高查询优化的运行效率

七、Semi Join消除

Semi Join的本质是对于外表,即LHS的每一条元组,若在内标也就是RHS的表中找到一条复合连接条件的元组,则表示连接成功,即使内表中有多个符合连接条件的元组,也只匹配一条。

  • 若内表能保证唯一性,Semi join可以转成Inner Join
八、提取新的约束条件

1.提取需要满足的条件

提取新的约束条件的时候还需要满足谓词下推的规则,这样新生成的约束条件才可以认为是过滤条件而下推给基表,PG数据库通过join_clause_is_movable_to函数来判断这种情况

2.提取流程

extract_or_clause函数中的主要提取流程是OR谓词连接的子约束条件

3.选择率修正

由于约束条件的改变,选择率的估算就会发生变化

  • 选择率的计算都是基于基表的统计信息进行的,而这些统计信息又是基于整个表的数据经过采样和统计分析得到的