PostgreSQL——查询优化——生成路径3

2021SC@SDUSC

概述

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客的分析内容:查询优化——生成路径
查询优化的整个过程可以分为预处理,生成路径和生成计划三个阶段。在上一篇博客中,我分析了查询优化的第二个阶段——生成路径,并详细的介绍和分析了生成基本关系的访问路径的方法。在这篇博客中我会详细的介绍生成索引扫描路径,生成TID扫描路径以及最终的汇总之前的所有生成路径生成最终路径。

生成索引扫描路径

当关系上涉及索引扫描时,要生成相应的索引路径。生成索引路径的函数主要是create_index_paths函数

create_index_paths函数

该函数为参数rel中指定的RelOptInfo结构添加可能的索引路径。

一个索引必须满足以下条件之一才会被考虑用于生成索引扫描路径:
能匹配一个或者多个约束条件(来自于baserestrictinfo字段中的RestrictInfo结构)
能匹配查询中的连接条件
能匹配查询的排序要求
能匹配查询的条件(where子句中除了连接条件的其他条件)

create_index_paths(PlannerInfo *root, RelOptInfo *rel)
{
	List	   *indexpaths;//存储索引路径的链表
	List	   *bitindexpaths;//位图索引路径
	List	   *bitjoinpaths;//位图路径
	List	   *joinorclauses;//链接子句
	IndexClauseSet rclauseset;//限制性条款
	IndexClauseSet jclauseset;//存放连接子句的集合
	IndexClauseSet eclauseset;//连接语句的等价类
	ListCell   *lc;//临时变量

	//判断有无索引,如果没有索引,就跳过整个过程
	if (rel->indexlist == NIL)
		return;

	//收集位图路径,然后在最后处理
	bitindexpaths = bitjoinpaths = joinorclauses = NIL;

	//依次检查每个索引
	foreach(lc, rel->indexlist)
	{
		IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);

		//在IndexClauseSets中保护有限大小的数组
		Assert(index->nkeycolumns <= INDEX_MAX_KEYS);

		 //忽略不符合查询的部分索引。
	
		if (index->indpred != NIL && !index->predOK)
			continue;

		//识别可以匹配索引的限制性条款
		MemSet(&rclauseset, 0, sizeof(rclauseset));
		match_restriction_clauses_to_index(root, index, &rclauseset);

		 //从限制性条款中建立索引路径,普通路径直接进入add_path(),位图路径被添加到bitindexpaths中
		
		get_index_paths(root, rel, index, &rclauseset,
						&bitindexpaths);

		 //识别可以匹配索引的连接子句,这一步只找到未被合并到等价类中的 "松散 "连接子句,还要收集连接OR子句以备后用。
		 
		MemSet(&jclauseset, 0, sizeof(jclauseset));
		match_join_clauses_to_index(root, rel, index,
									&jclauseset, &joinorclauses);

		 //寻找能够生成与索引相匹配的连接语句的等价类
		 
		MemSet(&eclauseset, 0, sizeof(eclauseset));
		match_eclass_clauses_to_index(root, index,
									  &eclauseset);

		//如果找到任何普通的或类的连接子句,用它们建立参数化的索引路径
		
		if (jclauseset.nonempty || eclauseset.nonempty)
			consider_index_join_clauses(root, rel, index,
										&rclauseset,
										&jclauseset,
										&eclauseset,
										&bitjoinpaths);
	}

	//为限制列表中任何合适的or子句生成BitmapOrPaths
	 //调用generate_bitmap_or_paths函数为baserestrictinfo中的OR子句生成BitmapOrPath路径,并将它们加入到bitindexpaths链表中
	indexpaths = generate_bitmap_or_paths(root, rel,
										  rel->baserestrictinfo, NIL);
	bitindexpaths = list_concat(bitindexpaths, indexpaths);

	 //同样地,为任何合适的OR句子生成BitmapOrPaths。连接语句列表中的任何合适的OR语句生成BitmapOrPath。 将这些添加到bitjoinpaths
	
	indexpaths = generate_bitmap_or_paths(root, rel,
										  joinorclauses, rel->baserestrictinfo);
	bitjoinpaths = list_concat(bitjoinpaths, indexpaths);

	 //如果找到了可用的东西,就为最有希望的限制位图索引路径组合生成一个BitmapHeapPath
	if (bitindexpaths != NIL)
	{
		Path	   *bitmapqual;
		BitmapHeapPath *bpath;

		bitmapqual = choose_bitmap_and(root, rel, bitindexpaths);
		//调用create_bitmap_heap_path函数在BitmapAndPath路径上生成一个BitHeapPath路径,然后尝试将它加入到pathlist
		bpath = create_bitmap_heap_path(root, rel, bitmapqual,
										rel->lateral_relids, 1.0, 0);
		//尝试将它加入到pathlist
		add_path(rel, (Path *) bpath);

		//创建一个部分位图堆路径
		if (rel->consider_parallel && rel->lateral_relids == NULL)
			create_partial_bitmap_paths(root, rel, bitmapqual);
	}

	//为每个不同的参数化生成一个这样的路径 在可用的位图索引路径中产生一个这样的路径
	if (bitjoinpaths != NIL)
	{
		List	   *path_outer;
		List	   *all_path_outers;
		ListCell   *lc;

		// path_outer持有bitjoinpaths中每个路径的参数化(以节省重新计算的次数),而all_path_outers 持有所有不同的参数化集。
		 
		path_outer = all_path_outers = NIL;
		foreach(lc, bitjoinpaths)//遍历bitjoinpaths获取参数化的路径
		{
			Path	   *path = (Path *) lfirst(lc);
			Relids		required_outer;

			required_outer = get_bitmap_tree_required_outer(path);
			path_outer = lappend(path_outer, required_outer);
			if (!bms_equal_any(required_outer, all_path_outers))
				all_path_outers = lappend(all_path_outers, required_outer);
		}

		//遍历all_path_outers,为每一个不同的参数化设置
		foreach(lc, all_path_outers)
		{
			Relids		max_outers = (Relids) lfirst(lc);
			List	   *this_path_set;
			Path	   *bitmapqual;
			Relids		required_outer;
			double		loop_count;
			BitmapHeapPath *bpath;
			ListCell   *lcp;
			ListCell   *lco;

			//识别所有的位图连接路径
			this_path_set = NIL;
			forboth(lcp, bitjoinpaths, lco, path_outer)
			{
				Path	   *path = (Path *) lfirst(lcp);
				Relids		p_outers = (Relids) lfirst(lco);

				if (bms_is_subset(p_outers, max_outers))
					this_path_set = lappend(this_path_set, path);
			}

			 //加入限制性位图路径,因为它们可以和任何连接路径一起使用
			this_path_set = list_concat(this_path_set, bitindexpaths);

			//为该参数化选择最佳的AND组合
			bitmapqual = choose_bitmap_and(root, rel, this_path_set);

			//下推路径
			required_outer = get_bitmap_tree_required_outer(bitmapqual);
			loop_count = get_loop_count(root, rel->relid, required_outer);
			bpath = create_bitmap_heap_path(root, rel, bitmapqual,
											required_outer, loop_count, 0);
			add_path(rel, (Path *) bpath);//加入路径到pathlist
		}
	}
}

create_index_paths函数调用的—— generate_bitmap_or_paths函数

generate_bitmap_or_paths函数
create_index_paths->generate_bitmap_or_paths函数从条件子句链表中寻找OR子句,如找到并且可以处理则生成BitmapOrPath.函数返回生成的链表BitmapOrPaths.

  //从条件子句链表中寻找OR子句,如找到并且可以处理则生成BitmapOrPath.
 //函数返回生成的链表BitmapOrPaths 
  //other_clauses是一个附加子句链表,
  //为了生成索引条件,可以假定为true,但不能用于搜索OR子句。
  
 static List *
 generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
                          List *clauses, List *other_clauses)
 {
     List       *result = NIL;
     List       *all_clauses;
     ListCell   *lc;
 
 
      // 使用当前和其他子句作为build_paths_for_OR函数的输入参数
      //从而不需要从列表中删除OR子句。
    
     all_clauses = list_concat(list_copy(clauses), other_clauses);//合并到链表中
 
     foreach(lc, clauses)//遍历子句链表
     {
         RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);//约束条件
         List       *pathlist;//路径链表
         Path       *bitmapqual;//
         ListCell   *j;

         if (!restriction_is_or_clause(rinfo))//不是OR子句,处理下一个子句
             continue;
 
        
          //必须能够将至少一个索引匹配到OR的某个分支,否则无法使用索引。
          
         pathlist = NIL;
         foreach(j, ((BoolExpr *) rinfo->orclause)->args)//遍历OR子句参数
         {
             Node       *orarg = (Node *) lfirst(j);//参数节点
             List       *indlist;
             //OR子句的参数必须是AND子句或者是子约束条件
             if (and_clause(orarg))//如为AND子句
             {
                 List       *andargs = ((BoolExpr *) orarg)->args;//获取AND子句的参数
 
                 indlist = build_paths_for_OR(root, rel,
                                              andargs,
                                              all_clauses);//构建路径
 
       
                 //递归调用generate_bitmap_or_paths,并添加到访问路径链表中
                 indlist = list_concat(indlist,
                                       generate_bitmap_or_paths(root, rel,
                                                                andargs,
                                                                all_clauses));
             }
             else
             {
                 RestrictInfo *rinfo = castNode(RestrictInfo, orarg);//不是AND,则为约束条件
                 List       *orargs;
 
                 Assert(!restriction_is_or_clause(rinfo));
                 orargs = list_make1(rinfo);
 
                 indlist = build_paths_for_OR(root, rel,
                                              orargs,
                                              all_clauses);//构建访问路径
             }
 
            
             if (indlist == NIL)
             {
                 pathlist = NIL;
                 break;
             }
 
             
              //选择最有希望的组合,并将其添加到路径列表中。
             
             bitmapqual = choose_bitmap_and(root, rel, indlist);
             pathlist = lappend(pathlist, bitmapqual);
         }
 
         
         // 如果左右两边都匹配,那么将它们转换为BitmapOrPath,并添加到结果列表中。
          
         if (pathlist != NIL)
         {
       //创建BitmapOrPath
             bitmapqual = (Path *) create_bitmap_or_path(root, rel, pathlist);
             result = lappend(result, bitmapqual);
         }
     }
 
     return result;
 }
 

生成TID扫描路径

在PostgreSQL中,TID用来表示元组的物理地址,一个TID从逻辑上可以看成是一个三元组(表OID,块号,元组的块内偏移量)。因为TID可以让执行器快速定位到磁盘上的元组,因此通过TID来访问元组非常高效。在PostgreSQL查询中也允许在where子句中使用TID作为查询条件,对于这种情况,查询规划器将为它生成TID扫描路径。TID扫描路径的生成主要由函数create_tidscan_paths完成。该函数主要分为两大步:
(1)从表的 RelOptInfo结构的baserestrictinfo中提取TID条件
(2)根据TID条件调用create_tidscan_paths函数生成TID扫描路径,并尝试用add_path加入到pathlist中。

create_tidscan_paths函数

create_tidscan_paths(PlannerInfo *root, RelOptInfo *rel)
{
	List	   *tidquals;

	// 如果在rel的baserestrict列表中存在任何合适的quals,就用它们生成一个普通的(无参数的)TidPath
	 //从表的 RelOptInfo结构的baserestrictinfo中提取TID条件,然后将提取到的TID条件放在tidquals字段中
	tidquals = TidQualFromRestrictInfoList(rel->baserestrictinfo, rel);//tid条件子句

	if (tidquals)
	{
		 //这条路径没有使用连接子句,但它仍然可能需要参数化,因为它的tlist中存在级联
		 
		Relids		required_outer = rel->lateral_relids;
      //添加tid路径
		add_path(rel, (Path *) create_tidscan_path(root, rel, tidquals,
												   required_outer));
	}

	 //尝试使用从等价类中提取的平等条款来生成参数化的TidPaths。从EquivalenceClasses中提取的平等条款

	if (rel->has_eclass_joins)
	{
		List	   *clauses;

		 //生成子句,跳过任何连接到级联参照的子句 
		clauses = generate_implied_equalities_for_column(root,
														 rel,
														 ec_member_matches_ctid,
														 NULL,
														 rel->lateral_referencers);

	//为每个可用的连接子句生成一个路径
		BuildParameterizedTidPaths(root, rel, clauses);
	}

	 //也可以考虑使用松散的连接参数的参数化TidPaths
	 
	BuildParameterizedTidPaths(root, rel, rel->joininfo);
}

生成最终路径

所有基本关系都创建好后,就可以把他们连接起来形成最终的连接关系,最终路径生成由make_rel_from_joinlist函数完成make_rel_from_joinlist函数会将传入参数joinlist中的基本关系连接起来生成最终的连接关系并且为最终的连接关系建立RelOptInfo结构,其pathlist字段就是最终路径组成的链表。该函数的实质就是选择不同的连接方式作为中间节点,选择不同的连接顺序将代表基本关系的叶子结点连接成一棵树,使其代价最小。这是一个递归生成二叉树的过程。

make_rel_from_joinlist函数

make_rel_from_joinlist(PlannerInfo *root, List *joinlist)
{	//传入参数List *joinlist:这个链表中每一个节点都代表一个基本关系,该基本关系可能表示一个范围表或者是一个子查询
	int			levels_needed;//动态规划算法所需的层数,和joinlist链表的长度相同
	List	   *initial_rels;//存储所有第一层关系的RelOptInfo结构
	ListCell   *jl;//临时变量 节点

	levels_needed = list_length(joinlist);//动态规划算法所需的层数,和joinlist链表的长度相同
//判断动态规划算法的层数
	if (levels_needed <= 0)//如果动态规划算法的层数为0则直接返回null
		return NULL;		

	initial_rels = NIL;//将initial_rels初始化为空即没有存储第一层关系的RelOptInfo结构
	foreach(jl, joinlist)//遍历joinlist链表
	{
		Node	   *jlnode = (Node *) lfirst(jl);
		RelOptInfo *thisrel;

		if (IsA(jlnode, RangeTblRef))//如果是一个基本关系
		{
			int			varno = ((RangeTblRef *) jlnode)->rtindex;

			thisrel = find_base_rel(root, varno);//将基本关系的RelOptInfo结构装入动态规划算法的第一层关系
		}
		else if (IsA(jlnode, List))//判断joinlist中的基本关系是否为一个子查询
		{
			//如果joinlist中的基本关系为一个子查询递归调用make_rel_from_joinlist函数为子查询生成路径
			thisrel = make_rel_from_joinlist(root, (List *) jlnode);
		}
		else
		{
			elog(ERROR, "unrecognized joinlist node type: %d",
				 (int) nodeTag(jlnode));
			thisrel = NULL;		
		}
//将所有第一层关系的RelOptInfo结构放入initial_rels链表中
		initial_rels = lappend(initial_rels, thisrel);
	}
//如果动态规划算法所需层数为1则表示from子句中只有一项那么可以将initial_rels中第一个节点对应的RelOptInfo作为最终的连接关系路径返回
	if (levels_needed == 1)
	{
		//将initial_rels中第一个节点对应的RelOptInfo作为最终的连接关系路径返回
		return (RelOptInfo *) linitial(initial_rels);
	}
	//如果动态规划算法所需层数>1
	else
	{
		//将initial_rels中存储的第一个节点对应的RelOptInfo记录到root中,方便后续的其他函数可以调用它
		root->initial_rels = initial_rels;

		if (join_search_hook)//使用全局变量join_search_hook调用的第三方函数来进行路径生成
			return (*join_search_hook) (root, levels_needed, initial_rels);
		else if (enable_geqo && levels_needed >= geqo_threshold)//启用了遗传算法并且动态规划算法所需层数超过了遗传算法的触发值,将利用遗传算法完成路径生成
			return geqo(root, levels_needed, initial_rels);
		else //使用standard_join_search函数实现动态规划算法
			return standard_join_search(root, levels_needed, initial_rels);
	}
}


总结

通过此篇源码分析,了解到了PostgreSQL中成索引扫描路径,生成TID扫描路径以及最终的汇总之前的所有生成路径生成最终路径的方法和相应函数,同时PostgreSQL——查询优化——生成路径也终于算是讲解分析完了。因为生成路径过程较为复杂,所以分析了很多篇才分析完。

感谢批评指正

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_47373497

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值