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

Posted weixin_47373497

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostgreSQL——查询优化——生成路径1相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

目录

概述

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客的分析内容:查询优化——生成路径
查询优化的整个过程可以分为预处理,生成路径和生成计划三个阶段。在上一篇博客中,我分析了查询优化的第一个阶段——预处理。这篇博客分析查询优化的第二个阶段——生成路径。在关系数据库中和非关系数据库对比,最大的特色是支持表的连接。但是表的连接结果会因连接顺序的不同而不同,所以同一组表的连接结果可能有多个。表的连接在逻辑上,我们可以看做是一棵连接树,每一棵连接树在PostgreSQL中都被称作是一条路径。因为同一组表的连接结果会有多条路径,所以查询优化的工作是:从一系列等效路径中选取效率最高的路径并形成执行计划

query_planner函数

生成路径的工作是由query_planner函数来完成的。为一个基本的查询(可能涉及连接)生成访问路径
query_planner函数执行流程

开始 初始化planner中的相关变量 Jointree是否为空 为查询中的基本关系构建RelOptInfo节点 处理目标链表和条件表达式 处理隐含的等式条件 规范化pathways 调用make_one_rel函数返回查询路径 确定cheapest path和sorted path的值 结束 报错
query_planner(PlannerInfo *root,
			  query_pathkeys_callback qp_callback, void *qp_extra)

	Query	   *parse = root->parse;//查询树
	List	   *joinlist;//连接表
	RelOptInfo *final_rel;//最后生成的路径结果存放在RelOptInfo结构体中返回

	//初始化planner中的相关变量
	root->join_rel_list = NIL;
	root->join_rel_hash = NULL;
	root->join_rel_level = NULL;
	root->join_cur_level = 0;
	root->canon_pathkeys = NIL;
	root->left_join_clauses = NIL;
	root->right_join_clauses = NIL;
	root->full_join_clauses = NIL;
	root->join_info_list = NIL;
	root->placeholder_list = NIL;
	root->fkey_list = NIL;
	root->initial_rels = NIL;

	//调用setup_simple_rel_arrays函数将范围表扁平化,以便更快地访问,并为索引基本关系设置一个空数组。
	setup_simple_rel_arrays(root);

	//判断jointree->fromlist是否为空,即该表是否为简单表而不是连接表
	Assert(parse->jointree->fromlist != NIL);
	//处理fromlist字段只有一个表的情况即无表连接
	if (list_length(parse->jointree->fromlist) == 1)
	          //取出连接表的fromlist字段,并将其转化为node节点形式
		Node	   *jtnode = (Node *) linitial(parse->jointree->fromlist);

		if (IsA(jtnode, RangeTblRef))//取出连接表的fromlist字段是否为范围表即from子句
		
			int			varno = ((RangeTblRef *) jtnode)->rtindex;//获取该节点在r_table即范围表中的位置
			RangeTblEntry *rte = root->simple_rte_array[varno];//根据上一步获得的r_table的位置,取出该表的RTE

			Assert(rte != NULL);//判断RTE是否为空
			if (rte->rtekind == RTE_RESULT)//判断RTE的种类是否为空的from子句
			
				//为查询中的基本表关系构建RelOptInfo节点
				final_rel = build_simple_rel(root, varno, NULL);

基本表就是 FROM 后指定的一个普通的表,而不是由连接等操作生成的表。


			//如果查询在一般情况下允许并行性,检查是否有并行限制。
				if (root->glob->parallelModeOK &&
					force_parallel_mode != FORCE_PARALLEL_OFF)
					final_rel->consider_parallel =
						is_parallel_safe(root, parse->jointree->quals);//判断是否是安全的并行

				//添加访问路径
				add_path(final_rel, (Path *)
						 create_group_result_path(root, final_rel,
												  final_rel->reltarget,
												  (List *) parse->jointree->quals));

				//选择最优的访问路径,并将最优路径放在最后返回的PlannerInfo结构体中
				set_cheapest(final_rel);
				//回调函数
				(*qp_callback) (root, qp_extra);
				//返回的PlannerInfo结构体中存放的最优路径
				return final_rel;
			
		
	
	//处理fromlist字段>1的情况即有多个表的连接的情况
	
	setup_append_rel_array(root);//用每个AppendRelInfo填充append rel数组,允许通过子relid直接查找

	//在 add_base_rels_to_query() 中初始化每个 RelOptInfo
	add_base_rels_to_query(root, (Node *) parse->jointree);

	
	 //检查targetlist和join树,为所有引用的占位符添加条目到baserel targetlists,并为所有引用的占位符生成占位信息条目。
	build_base_rel_tlists(root, root->processed_tlist);

    //查找级联引用
	find_lateral_references(root);
  //deconstruct_jointree() 调用完成后,会返回一个上述的连接链表,同时针对每个基本表的扫描或连接限制条件,都存入了各自的 RelOptInfo 的 baserestrictinfo/joininfo 中
	joinlist = deconstruct_jointree(root);

	 //处理隐含的等式条件
	generate_base_implied_equalities(root);

	 //规范化pathkeys
	(*qp_callback) (root, qp_extra);

	//移除没有用上的外部连接
	joinlist = remove_useless_joins(root, joinlist);
	//将任何具有唯一内部关系的半连接减少为普通的内部连接
	reduce_unique_semijoins(root);
	//构建级联参考
	create_lateral_join_info(root);

	// 将外键与等价类相匹配并添加配额。这一步必须在最终确定等价类后进行,这样就可以跳过处理参与删除关系的外键。
	match_foreign_keys_to_quals(root);
	//提取or子句的限制条件
	extract_restriction_or_clauses(root);

	//现在通过添加其他表来扩展
	add_other_rels_to_query(root);

	 //调用make_one_rel函数返回查询路径
	final_rel = make_one_rel(root, joinlist);
    //检查是否得到可用的路径
	if (!final_rel || !final_rel->cheapest_total_path ||
		final_rel->cheapest_total_path->param_info != NULL)
		//没有可用路径则报错
		elog(ERROR, "failed to construct the join relation");
	//返回的PlannerInfo结构体中存放的最优路径
	return final_rel;

RelOptInfo结构体

RelOptInfo结构体是贯穿整个路径生成过程的一个数据结构,生成路径的最终结果始终存放其中,生成和选择路径所需的许多数据也存放在其中。路径生成和选择涉及的所有操作,几乎都是针对RelOptInfo结构体操作。

RelOptInfo结构体涉及baserel(基本关系)和joinrel(连接关系)。baserel(基本关系)是一个普通表,子查询或者范围表中出现的函数。joinrel(连接关系)是两个或者两个以上的baserel(基本关系)在一定约束条件下的简单合并。对任何一组baserel仅有一个joinrel,即对于任何给定的baserel集合,只有一个RelOptInfo结构体。例如:连接a,b,c是用同一个RelOptInfo结构体表示,无论a,b,c这三个baserel连接的顺序是什么样的。因为对于joinrel(连接关系)来说,baserel之间是没有顺序的。至于这三个baserel之间是以什么样的路径(连接顺序,方式)连接成a,b,c形式的连接,则记录在RelOptInfo结构体中的pathlist字段中。

typedef struct RelOptInfo

	NodeTag		type;//节点类型

	RelOptKind	reloptkind;//关系的类型(基本关系类型,连接关系类型,其他成员关系类型)

	Relids		relids;		//关系所涉及的基本表(在范围表中的编号)

	double		rows;		//估计的元组数量
	bool		consider_startup;	//是否考虑启动成本?是,需要保留启动成本低的路径
	bool		consider_param_startup; //是否考虑参数化?的路径
	bool		consider_parallel;	//是否考虑并行处理路径

	List	   *pathlist;	//多个基本关系生成该关系的路径
	List	   *ppilist;	  //路径链表中的ParamPathInfos链表
	List	   *partial_pathlist;	  //并行部分路径
	struct Path *cheapest_startup_path;//最小的启动代价的路径
	struct Path *cheapest_total_path;//最小的总代价的路径
	struct Path *cheapest_unique_path;//最小的唯一代价的路径
	List	   *cheapest_parameterized_paths; //参数化代价最低的路径

	Relids		direct_lateral_relids;	//使用lateral语法,需依赖的Relids
	Relids		lateral_relids;  //rel的最小化参数信息
	
	Index		relid;//当前关系在范围表中的编号
	Oid			reltablespace;	/* containing tablespace */
	RTEKind		rtekind;		//基本关系的类型(普通关系,子查询,函数等)
	AttrNumber	min_attr;		//关系中最小的字段编号
	AttrNumber	max_attr;		//关系中最大的字段编号
	Relids	   *attr_needed;	//连接关系中哪些字段是需要的
	int32	   *attr_widths;	//每个字段的宽度的估计
	List	   *lateral_vars;	  //关系依赖的LATERAL Vars/PHVs
	Relids		lateral_referencers;	  //依赖该关系的Relids
	List	   *indexlist;		//索引关系的信息
	List	   *statlist;		 //统计信息链表
	BlockNumber pages;		//关系占用的页面数
	double		tuples;     //关系的元组数
	PlannerInfo *subroot;		//如为子查询,存储子查询的root
	List	   *subplan_params;  //如为子查询,存储子查询的参数
	int			rel_parallel_workers;	 //并行执行,需要多少个workers,记录workers数量
	Oid			serverid;		 //表或连接的服务器标识符
	Oid			userid;			  //用户id标识
	bool		useridiscurrent;	  //标识当前用户,对于当前用户来说,连接才是有效的
	struct FdwRoutine *fdwroutine;    //使用结构体FdwRoutine,避免包含头文件fdwapi.h

	List	   *unique_for_rels;	 //已知的,可保证唯一的Relids链表
	List	   *non_unique_for_rels;	//已知的,不唯一的Relids链表
	
	List	   *baserestrictinfo;	//如为基本关系,存储约束条件链表
	QualCost	baserestrictcost;	 //解析约束表达式的成本
	Index		baserestrict_min_security;	 //在baserestrictinfo中最低安全等级
	List	   *joininfo;	//连接子句中调用该关系时的约束信息
	bool		has_eclass_joins;	//是否存在等值连接

	bool		consider_partitionwise_join;	  //是否考虑使用partitionwise join
	Relids		top_parent_relids;	  //最高层的父关系Relids

	PartitionScheme part_scheme; //分区的schema 
	int			nparts;			//分区数 
	struct PartitionBoundInfoData *boundinfo;	 //分区边界信息 
	List	   *partition_qual;  //分区约束
	struct RelOptInfo **part_rels;	  //分区的RelOptInfo数组
	List	  **partexprs;		   //非空分区键表达式链表
	List	  **nullable_partexprs;  //可为空的分区键表达式 
	List	   *partitioned_child_rels; // 分区子表RT Indexes链表 
 RelOptInfo;

Path结构体

在上面介绍的RelOptInfo中,变量pathlist记录的是生成该RelOptInfo在某方面较优的路径,其中路径中每一个节点都是一个path结构,path结构也成为路径。在pathlist中,每一个path节点对应的具体路径节点则存放的是构成该路径的具体信息,包括表连接的方式,顺序,以及参加连接的baserel的访问方式。

typedef struct Path

	NodeTag		type;    //节点类型

	NodeTag		pathtype;		//标记扫描/连接方法

	RelOptInfo *parent;			//此路径所构建的关系
	double		rows;			//估计的结果元组的数量
	Cost		startup_cost;	//执行此路径的启动代价估计
	Cost		total_cost;		//执行此路径的总代价估计
	List	   *pathkeys;		//此路径的输出的排序信息
 Path;

query_planner函数调用的——make_one_rel函数

make_one_rel函数找出执行查询的所有可能访问路径。make_one_rel函数分为两个阶段:生成扫描路径(set_base_rel_pathlists)和生成连接路径(make_rel_from_joinlist).

make_one_rel(PlannerInfo *root, List *joinlist)
	
//传入参数List *joinlist:这个链表中每一个节点都代表一个基本关系,该基本关系可能表示一个范围表或者是一个子查询

	RelOptInfo *rel;//RelOptInfo结构体,存储最终路径以及路径查找的相关信息
	Index		rti;//记录当前关系在范围表中的编号
	double		total_pages; //关系占用的页面数

	//构建All_baserels Relids集合
	root->all_baserels = NULL;//初始将它置空
	for (rti = 1; rti < root->simple_rel_array_size; rti++)//遍历RelOptInfo
	
		RelOptInfo *brel = root->simple_rel_array[rti];//取得关系

		//检查上一步获得的brel即获取的关系是否为空
		if (brel == NULL)
			continue;
      //检查该关系是否在范围表中
		Assert(brel->relid == rti);

		//如果不是brel的reloptkind字段的类型不是基本表则忽略(说明该关系是连接关系而不是基本关系)
		if (brel->reloptkind != RELOPT_BASEREL)
			continue;
       //如果是基本表的话,则将它加入基本表链表中
		root->all_baserels = bms_add_member(root->all_baserels, brel->relid);
	

	//设置RelOptInfo的consider_param_startup变量,是否考量fast-start plans
	set_base_rel_consider_startup(root);
   //估算Relation的Size并且设置consider_parallel标记
	set_base_rel_sizes(root);

	 以上是关于PostgreSQL——查询优化——生成路径1的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL——查询优化——生成优化计划2

PostgreSQL——代价估计

PostgreSQL——代价估计

PostgreSQL——代价估计

PostgreSQL——代价估计

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