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

2021SC@SDUSC

概述

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客的分析内容:查询优化——生成路径
查询优化的整个过程可以分为预处理,生成路径和生成计划三个阶段。在上一篇博客中,我分析了查询优化的第二个阶段——生成路径。但是我们在上篇博客中只是简单的分析了一下生成路径的大概流程以及生成路径的两个基本算法:动态规划算法和遗传算法。在这篇博客中我会详细的介绍和分析生成基本关系的访问路径 在上一篇博客中我有分析set_base_rel_pathlists函数,经过上一篇的分析可以知道set_base_rel_pathlists函数负责生成路径的第一个阶段:生成基本关系的访问路径——即为每一个基本关系生成一个RelOptInfo结构并生成路径,放在RelOptInfo结构的pathlist字段中。set_base_rel_pathlists函数是负责为基本关系生成访问路径的,但是基本关系的类型就有很多种,所以对于不同类型的基本关系又需要通过不同的函数去完成访问路径的生成。即set_base_rel_pathlists函数又需要调用不同的函数分别为不同类型的基本关系生成访问路径。
基本关系的类型见下表

基本关系类型注释相应函数
子关系RTE 的inh字段为真set_append_rel_pathlist
RTE_SUBQUERY表示当前的基本关系是一个子查询set_subquery_pathlist
RTE_FUNCTION表示当前的基本关系是一个函数set_function_pathlist
RTE_VALUES表示当前的基本关系是一个VALUES列表set_values_pathlist
RTE_CTE表示当前的基本关系是一个公共表表达式如果是递归的set_worktable_pathlist,否则set_cte_pathlist
RTE_RELATION表示当前的基本关系是一个普通表set_plain_rel_pathlist

set_append_rel_pathlist

如果一个基本关系是子关系即它的RTE的inh字段为真(RTE的介绍参加前几篇博客)则调用set_append_rel_pathlist函数为子关系建立访问路径。由于子关系的元组的获得必须要依赖于其父关系的元组,因此要将它的父关系的访问路径作为一种AppendPath(附加路径)加入到子关系的路径节点下层。

set_append_rel_pathlist函数用到的—— AppendRelInfo结构体

AppendRelInfo链表指示在展开父节点时必须包含哪些子rte,每个节点具有将引用父节点的Vars转换为引用该子节点的Vars所需的所有信息

typedef struct AppendRelInfo
{
  //当我们将可继承表(分区表)或UNION-ALL子查询展开为“追加关系”(本质上是子RTE的链表)时, 需要为每个子RTE构建一个AppendRelInfo结构体。这些结构体保存在PlannerInfo节点的append_rel_list中
	NodeTag		type;//节点类型
	Index		parent_relid;	 //父表的RT索引
	Index		child_relid;	 //子表的RT索引
	Oid			parent_reltype;//记录父亲节点表类型的OID
	Oid			child_reltype;	//记录孩子节点表类型的OID
	List	   *translated_vars;	//child's Vars中的表达式
	//将父表的OID存储在这里用于继承
	Oid			parent_reloid;	//父表的oid
} AppendRelInfo;
set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
						Index rti, RangeTblEntry *rte)
{
	int			parentRTindex = rti;
	List	   *live_childrels = NIL;
	ListCell   *l;

	//为每个成员关系生成访问路径,并"记住"非虚拟子节点
	foreach(l, root->append_rel_list)//遍历链表,寻找父关系
	{
	//为父关系对应的基本关系生成RelOptInfo结构体,并在其中生成父关系的路径
		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);//获取AppendRelInfo

		int			childRTindex;
		RangeTblEntry *childRTE;
		RelOptInfo *childrel;

		//append_rel_list含有所有的append relations,忽略其他的rels
		if (appinfo->parent_relid != parentRTindex)
			continue;

	 //重新定位子RTE和RelOptInfo
		childRTindex = appinfo->child_relid;
		childRTE = root->simple_rte_array[childRTindex];
		childrel = root->simple_rel_array[childRTindex];

		//如果set_append_rel_size()函数在访问了子关系后,在某些点上断定父append relation是非并行安全的,
         //则需要分发不安全的标记到子关系中,以免产生无用的部分访问路径.
		if (!rel->consider_parallel)
			childrel->consider_parallel = false;

		//计算子关系的访问路径
		set_rel_pathlist(root, childrel, childRTindex, childRTE);

		//如果为虚拟关系,则忽略
		if (IS_DUMMY_REL(childrel))
			continue;
			
		//添加到live_childrels链表中
		live_childrels = lappend(live_childrels, childrel);
	}

	 //添加访问路径到append relation中
	 //调用add_paths_to_append_rel函数,将父关系的总代价的最优路径包装成一个appendpath,并将它作为子关系的路径加入到它的 RelOptInfo结构中
	add_paths_to_append_rel(root, rel, live_childrels);
}

set_subquery_pathlist

如果一个基本关系是子查询则调用set_subquery_pathlist函数为子查询建立访问路径。set_subquery_pathlist函数为子查询的RelOptInfo建立访问路径。如果子查询被附加了约束条件(baserestrictinfo字段中指定),则首先将他们下推到子查询本身的where或者having子句中,这样会得到更好的子计划。

set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
					  Index rti, RangeTblEntry *rte)
{
	Query	   *parse = root->parse;
	Query	   *subquery = rte->subquery;
	Relids		required_outer;
	pushdown_safety_info safetyInfo;
	double		tuple_fraction;
	RelOptInfo *sub_final_rel;
	ListCell   *lc;

	//必须复制查询,以便规划不会弄乱RTE内容
	subquery = copyObject(subquery);

	//如果它是一个LATERAL子查询,它可能包含当前查询级别的一些Vars,要求它被视为参数化
	required_outer = rel->lateral_relids;

	//将subquery_is_pushdown_safe的结果区域清零,这样它就可以在递归过程中根据需要设置标志
	memset(&safetyInfo, 0, sizeof(safetyInfo));
	safetyInfo.unsafeColumns = (bool *)
		palloc0((list_length(subquery->targetList) + 1) * sizeof(bool));

	//如果子查询有 "security_barrier "标志,这意味着子查询来源于一个必须执行行级安全的视图。 那就不能推倒包含泄漏函数的quals
	safetyInfo.unsafeLeaky = rte->security_barrier;

	 //检查baserestrictinfo中的每一个约束条件,如果通过了subquery_is_pushdown_safe检查,则该约束条件是可以下推的,则下推该条件到子查询中
	if (rel->baserestrictinfo != NIL &&
		subquery_is_pushdown_safe(subquery, subquery, &safetyInfo))
	{
		//记录待下降的条件
		List	   *upperrestrictlist = NIL;
		ListCell   *l;
      //遍历链表对每一个约束条件进行下推
		foreach(l, rel->baserestrictinfo)
		{
			RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
			Node	   *clause = (Node *) rinfo->clause;
				//判断是否是下推安全的
			if (!rinfo->pseudoconstant &&
				qual_is_pushdown_safe(subquery, rti, clause, &safetyInfo))
			{
			//对可以下推的限制条件执行下推
				subquery_push_qual(subquery, rte, rti, clause);
			}
			else
			{
				//不能下推则放在上层的查询中
				upperrestrictlist = lappend(upperrestrictlist, rinfo);
			}
		}
		rel->baserestrictinfo = upperrestrictlist;
	}
   //根据安全信息,释放掉不安全的列的内存
	pfree(safetyInfo.unsafeColumns);

	//删除子查询输出列中,在主查询用不到的列
	remove_unused_subquery_outputs(subquery, rel);

	//如果外层没有连接、聚合或排序,则可以安全地将外层的tuple_fraction向下传递给子查询。否则,子查询需要计划进行全面检索
	if (parse->hasAggs ||
		parse->groupClause ||
		parse->groupingSets ||
		parse->havingQual ||
		parse->distinctClause ||
		parse->sortClause ||
		has_multiple_baserels(root))
		tuple_fraction = 0.0;	
	else
		tuple_fraction = root->tuple_fraction;

	//判断是否使用了plan_params,如果使用则会报错,因为plan_params不应该在当前查询级别中使用
	Assert(root->plan_params == NIL);

	//调用 subquery_planner函数生成此子查询的的子计划,并放入子查询的RelOptInfo结构的subplan字段中
	//获取子查询的执行计划
	rel->subroot = subquery_planner(root->glob, subquery,
									root,
									false, tuple_fraction);

	//隔离该特定子计划所需的参数
	rel->subplan_params = root->plan_params;
	root->plan_params = NIL;//plan_params置空

	//有可能是约束条件的排除证明子查询是空的。如果是这样,产生一个不加修饰的假路径,就可以在这个查询层面上做适当的优化
	sub_final_rel = fetch_upper_rel(rel->subroot, UPPERREL_FINAL, NULL);

	if (IS_DUMMY_REL(sub_final_rel))
	{
		set_dummy_rel_pathlist(rel);
		return;
	}

	//设置子查询的估算信息
	set_subquery_size_estimates(root, rel);
   //对于subquery_planner产生的每个Path,在外层查询中制作一个SubqueryScanPath 在外部查询中
	foreach(lc, sub_final_rel->pathlist)
	{
		Path	   *subpath = (Path *) lfirst(lc);
		List	   *pathkeys;

		//把 pathkeys转换成外层的表示
		//当排序键中有volatile函数的情况,这个排序键在外面用到了且在内部是在sort语句中,就生成pathkey
		//当排序键中没有volatile函数的情况,就取子查询的targetlist里对应的pathkey等价类成员最多的那个生成pathkey
				pathkeys = convert_subquery_pathkeys(root,
											 rel,
											 subpath->pathkeys,
											 make_tlist_from_pathtarget(subpath->pathtarget));

		//通过子查询路径生成外层路径访问
		// 调用create_subqueryscan_path函数生成类型为T_SubqueryScan的路径,其执行代价与RelOptInfo结构的subplan字段中记录的子计划相同
		add_path(rel, (Path *) create_subqueryscan_path(root, rel, subpath,
										  pathkeys, required_outer));
	}

	//判断外部关系是否允许并行
	if (rel->consider_parallel && bms_is_empty(required_outer))
	{
		//如果conside_parallel为false,就不应该有部分路径
		Assert(sub_final_rel->consider_parallel ||
			   sub_final_rel->partial_pathlist == NIL);

		//对部分路径做相同处理
		foreach(lc, sub_final_rel->partial_pathlist)
		{
			Path	   *subpath = (Path *) lfirst(lc);
			List	   *pathkeys;

			//把pathkey转换成外层的表示
			pathkeys = convert_subquery_pathkeys(root,
												 rel,
												 subpath->pathkeys,
												 make_tlist_from_pathtarget(subpath->pathtarget));

			//当外层可并行时
			//如果上层的pathkey与本层不同的需要把这个路径加在partial_pathlist中
			//如果本层键包含上层键,且路径的成本更低,就同意把新路径加进去,但之前要先把上层键中属于本层键子集的都删掉
			add_partial_path(rel, (Path *)
							 create_subqueryscan_path(root, rel, subpath,
													  pathkeys,
													  required_outer));
		}
	}
}

set_function_pathlist

该函数为函数类型的RTE建立访问路径。

set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
	Relids		required_outer;
	List	   *pathkeys = NIL;

//不支持将连接子句推入函数扫描的量词中,但由于函数表达式中的LATERAL refs,它仍然可能需要参数化。
	required_outer = rel->lateral_relids;

	//判断是否为函数类型的RTE,如果是函数类型的RTE,则为它建立访问路径
	if (rte->funcordinality)
	{
		AttrNumber	ordattno = rel->max_attr;
		Var		   *var = NULL;
		ListCell   *lc;

	//检查在rel的目标列表中是否有它的Var 如果没有,那么该查询没有引用ordinality列
		foreach(lc, rel->reltarget->exprs)//遍历表达式
		{
			Var		   *node = (Var *) lfirst(lc);

			//检查在rel的目标列表中是否有它的Var 
			if (IsA(node, Var) &&
				node->varattno == ordattno &&
				node->varno == rel->relid &&
				node->varlevelsup == 0)
			{
				var = node;
				break;
			}
		}

		//用int8排序为这个Var建立pathkeys
		if (var)
			pathkeys = build_expression_pathkey(root,
												(Expr *) var,
												NULL,	
												Int8LessOperator,
												rel->relids,
												false);
	}

	//调用函数add_path将 create_functionscan_path函数产生的T_FunctionScan路径加入到pathlist
	add_path(rel, create_functionscan_path(root, rel,
										   pathkeys, required_outer));
}

set_values_pathlist

该函数为VALUES列表生成路径,它的过程和set_function_pathlist函数类似,只是这个函数要调用create_valuesscan_path函数来生成一个T_ValuesScan路径

set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
	Relids		required_outer;

	//不支持将连接子句推入函数扫描的量词中,但由于函数表达式中的LATERAL refs,它仍然可能需要参数化。
	required_outer = rel->lateral_relids;

	//调用create_valuesscan_path函数来生成一个T_ValuesScan路径
	add_path(rel, create_valuesscan_path(root, rel, required_outer));
}

递归CTE——set_worktable_pathlist

当一个CTE是递归的,则要单独为它生成一个T_workTableScan路径以配合RecursiveUnion节点的扫描

set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
	Path	   *ctepath;
	PlannerInfo *cteroot;
	Index		levelsup;
	Relids		required_outer;
     //找到递归项的路径
	 //首先要找到CTE对应的非递归计划
	levelsup = rte->ctelevelsup;//记录上层的查询等级
	if (levelsup == 0)			//上层的查询等级不能等于0,否则报错
		elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
	levelsup--;
	cteroot = root;
	while (levelsup-- > 0)//递归处理CTE,每一次递归完成就将上层的查询等级减1
	{
		cteroot = cteroot->parent_root;//上一层的节点不能为空,否则报错
		if (!cteroot)			
			elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
	}
	ctepath = cteroot->non_recursive_path;
	if (!ctepath)				//如果上层节点没有递归路径则报错
		elog(ERROR, "could not find path for CTE \"%s\"", rte->ctename);

	//调用set_cte_size_estimates函数根据该计划的信息来设置CTE的RelOptInfo
	set_cte_size_estimates(root, rel, ctepath->rows);
//不支持将连接子句推入函数扫描的量词中,但由于函数表达式中的LATERAL refs,它仍然可能需要参数化。
	required_outer = rel->lateral_relids;

	//调用create_worktablescan_path函数创建一个T_workTableScan路径并加入到pathlist中
	add_path(rel, create_worktablescan_path(root, rel, required_outer));
}

非递归CTE——set_cte_pathlist

如果CTE是非递归的,则可以把每一个CTE表达式作为一个独立存在的虚表,该函数对其中每一个CTE表达式检查是否存在计划之后,设置代价信息并调用create_ctescan_path函数创建一个T_CteScan路径并加入到pathlist中。

set_cte_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
	Plan	   *cteplan;
	PlannerInfo *cteroot;
	Index		levelsup;
	int			ndx;
	ListCell   *lc;
	int			plan_id;
	Relids		required_outer;

	//找到被引用的CTE,并找到以前为它制定的计划
	levelsup = rte->ctelevelsup;
	cteroot = root;
	while (levelsup-- > 0)//排除掉递归的CTE
	{
		cteroot = cteroot->parent_root;//记录父节点
		if (!cteroot)		//如果父节点为空则会报错
			elog(ERROR, "bad levelsup for CTE \"%s\"", rte->ctename);
	}

	
	ndx = 0;
	foreach(lc, cteroot->parse->cteList)//遍历每个非递归的CTE
	{
		CommonTableExpr *cte = (CommonTableExpr *) lfirst(lc);//从链表中取得CTE表达式

		if (strcmp(cte->ctename, rte->ctename) == 0)//比较CTE和RTE的名字是否相同
			break;
		ndx++;
	}
	if (lc == NULL)				//判断有无CTE
		elog(ERROR, "could not find CTE \"%s\"", rte->ctename);
	if (ndx >= list_length(cteroot->cte_plan_ids))判断有无CTE计划
		elog(ERROR, "could not find plan for CTE \"%s\"", rte->ctename);
	plan_id = list_nth_int(cteroot->cte_plan_ids, ndx);

	//调用set_cte_size_estimates函数根据该计划的信息来设置CTE的RelOptInfo
	set_cte_size_estimates(root, rel, cteplan->plan_rows);

	//不支持将连接子句推入函数扫描的量词中,但由于函数表达式中的LATERAL refs,它仍然可能需要参数化。
	required_outer = rel->lateral_relids;

	//调用create_ctescan_path函数创建一个T_CteScan路径并加入到pathlist中
	add_path(rel, create_ctescan_path(root, rel, required_outer));
}

set_plain_rel_pathlist

该函数用于生成普通关系(非子查询,且无继承关系)的路径。

set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
	Relids		required_outer;

	//不支持将连接子句推入函数扫描的量词中,但由于函数表达式中的LATERAL refs,它仍然可能需要参数化。
	required_outer = rel->lateral_relids;

	//考虑顺序扫描
	//调用create_seqscan_path函数生成顺序扫描路径,并调用add_path函数加入到pathlist中
	add_path(rel, create_seqscan_path(root, rel, required_outer, 0));
//判断是否可以并行化顺序扫描
	if (rel->consider_parallel && required_outer == NULL)
		create_plain_partial_paths(root, rel);

	//检查基本关系上是否有索引可用,为可用的索引建立索引扫描路径并尝试用add_path加入到pathlist
	create_index_paths(root, rel);
	
//调用create_tidscan_paths函数尝试生成TID扫描路径
//注意:这里生成的索引扫描路径和TID扫描路径不一定会保留在pathlist中,除非他们在代价或者排序上具有优势

	create_tidscan_paths(root, rel);
}

总结

通过此篇源码分析,了解到了PostgreSQL查询优化的生成路径阶段的第一步——为基本关系生成访问路径。

感谢批评指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_47373497

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

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

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

打赏作者

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

抵扣说明:

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

余额充值