PostgreSQL——查询优化——生成优化计划2
Posted weixin_47373497
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostgreSQL——查询优化——生成优化计划2相关的知识,希望对你有一定的参考价值。
2021SC@SDUSC
目录
概述
我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客的分析内容:查询优化——生成计划
查询优化的整个过程可以分为预处理,生成路径和生成计划三个阶段。在上一篇博客中我分析了可优化的MIN/MAX聚集计划和普通计划的生成在这篇博客中我会分析生成完整计划
上篇博客介绍了依据最优路径best_path生成计划树的过程。之前讲过,生成路径仅仅考虑基本查询语句信息,并没有保留诸如GROUP BY,ORDER BY等信息。grouping_planner函数调用create_plan生成基本计划树后,则会依据查询树相关约束信息在前面生成的普通计划之上添加相应的计划节点生成完整计划。
生成完整计划的过程主要包含两个大步骤:创建聚集计划节点,创建排序计划节点
生成完整计划
创建聚集计划节点
通过调用函数make_agg,生成聚集计划节点,函数返回一个类型位Agg的结构
make_agg函数执行流程
make_agg函数
make_agg(List *tlist, List *qual,
AggStrategy aggstrategy, AggSplit aggsplit,
int numGroupCols, AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations,
List *groupingSets, List *chain,
double dNumGroups, Plan *lefttree)
//传入参数分析:
//AggStrategy aggstrategy:类型为AggStrategy,指该聚集计划采用的聚集策略。聚集策略有:AGG_PLAIN(普通聚集)AGG_SORTED(排序聚集),AGG_HASHED(Hash聚集)
//int numGroupCols:类型为int,需要聚集的属性数目
//AttrNumber *grpColIdx:类型为AttrNumber指针类型,代表聚集属性的索引
//Oid *grpOperators:类型为OID指针类型,代表用于分组时的排序操作符
//Plan *lefttree:为Plan的指针类型(可以是实际的计划类型强制转换成Plan类型)代表添加聚集计划节点前的计划树,该计划树作为Agg的左子树
Agg *node = makeNode(Agg);//产生agg节点
Plan *plan = &node->plan;
long numGroups;
/* Reduce to long, but 'ware overflow! */
numGroups = (long) Min(dNumGroups, (double) LONG_MAX);
//初始化计划节点
node->aggstrategy = aggstrategy;//初始化聚集策略
node->aggsplit = aggsplit;
node->numCols = numGroupCols;//初始化分组属性列数
node->grpColIdx = grpColIdx;//初始化分组列的索引
node->grpOperators = grpOperators;//初始化分组操作符
node->grpCollations = grpCollations;
node->numGroups = numGroups;//初始化预计分组的数目
node->aggParams = NULL; //调用SS_finalize_plan()函数填充这个
node->groupingSets = groupingSets;
node->chain = chain;
plan->qual = qual;
plan->targetlist = tlist;//初始化目标列
plan->lefttree = lefttree;//初始化左子树
plan->righttree = NULL;
return node;
make_agg函数返回的结构体——Agg
typedef struct Agg
Plan plan; //计划树
AggStrategy aggstrategy; //聚集策略
int numCols; //分组属性列数
AttrNumber *grpColIdx; //分组列的索引
Oid *grpOperators; //分组操作符
Oid *grpCollations;
long numGroups; //预计分组数目
Bitmapset *aggParams; //Aggref输入中使用的参数ID
//规划器仅在HASHED/MIXED情况下提供numGroups和aggParams
List *groupingSets; //要使用的分组集
List *chain; //链式聚合/排序节点
Agg;
通过数据结构可以看出来,Agg计划节点除了包含Plan类型成员变量外,还添加了和聚集分组相关的一些成员变量,包括分组属性数目,分组属性的索引,分组的操作符等,这些扩展成员变量由外部调用传入参数直接赋值;获得左子树的代价(仅保留大小估计,时间代价会被覆盖),调用cost_agg计算他的聚集代价,对于存在HAVING子句的情况调用cost_qual_eval估计它的代价;最后将代价,目标属性,HAVING子句赋给plan相应的成员变量(成员变量的解释在前一篇博客的顺序扫描计划中),并将原有计划树作为它的左子树添加进来形成完整的计划树。
make_agg函数调用——copy_plan_costsize函数——获得左子树代价
copy_plan_costsize(Plan *dest, Plan *src)
dest->startup_cost = src->startup_cost;//为开始的代价赋值
dest->total_cost = src->total_cost;//给总代价赋值
dest->plan_rows = src->plan_rows;//记录计划的行数
dest->plan_width = src->plan_width;//记录计划的宽度
//假设被插入的节点不具有并行能力
dest->parallel_aware = false;
//假设插入的节点是并行安全的
dest->parallel_safe = src->parallel_safe;
make_agg函数调用——cost_agg函数——计算聚集代价
cost_agg(Path *path, PlannerInfo *root,
AggStrategy aggstrategy, const AggClauseCosts *aggcosts,
int numGroupCols, double numGroups,
List *quals,
Cost input_startup_cost, Cost input_total_cost,
double input_tuples)
double output_tuples;
Cost startup_cost;
Cost total_cost;
AggClauseCosts dummy_aggcosts;
//如果传入NULL,则使用全部为零的代价
if (aggcosts == NULL)
Assert(aggstrategy == AGG_HASHED);
MemSet(&dummy_aggcosts, 0, sizeof(AggClauseCosts));
aggcosts = &dummy_aggcosts;
//aggcosts的transCost.per_tuple部分对每个输入元组计算一次代价,finalCost.per_tuple部分对每个输出元组计算一次代价,这些计算的代价对应于评估最终表达式的值
//如果要进行分组,需要对每个分组列和每个输入元组计算额外的cpu_operator_cost代价
//如果不分组,将产生一个单一的输出元组,需要对每个输出元组收取cpu_tuple_cost代价
//GG_SORTED和AGG_HASHED具有完全相同的总的CPU成本相同,但AGG_SORTED的启动成本较低。 如果输入路径已经被适当的排序,AGG_SORTED应该是首选(因为它没有内存风险)
if (aggstrategy == AGG_PLAIN)
startup_cost = input_total_cost;
startup_cost += aggcosts->transCost.startup;
startup_cost += aggcosts->transCost.per_tuple * input_tuples;
startup_cost += aggcosts->finalCost.startup;
startup_cost += aggcosts->finalCost.per_tuple;
//不是一个分组
total_cost = startup_cost + cpu_tuple_cost;
output_tuples = 1;
else if (aggstrategy == AGG_SORTED || aggstrategy == AGG_MIXED)
//在这里能够提供及时的输出
startup_cost = input_startup_cost;
total_cost = input_total_cost;
if (aggstrategy == AGG_MIXED && !enable_hashagg)
startup_cost += disable_cost;
total_cost += disable_cost;
//这样的计算方式与HASHED情况相匹配
total_cost += aggcosts->transCost.startup;
total_cost += aggcosts->transCost.per_tuple * input_tuples;
total_cost += (cpu_operator_cost * numGroupCols) * input_tuples;
total_cost += aggcosts->finalCost.startup;
total_cost += aggcosts->finalCost.per_tuple * numGroups;
total_cost += cpu_tuple_cost * numGroups;
output_tuples = numGroups;
//如果有资格证,要考虑其成本和选择性
if (quals)
QualCost qual_cost;
cost_qual_eval(&qual_cost, quals, root);
startup_cost += qual_cost.startup;
total_cost += qual_cost.startup + output_tuples * qual_cost.per_tuple;
output_tuples = clamp_row_est(output_tuples *
clauselist_selectivity(root,
quals,
0,
JOIN_INNER,
NULL));
path->rows = output_tuples;
path->startup_cost = startup_cost;
path->total_cost = total_cost;
make_agg函数调用——cost_qual_eval函数——计算Having字句代价
cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root)
//输入可以是一个隐式AND的布尔表达式列表,也可以是一个RestrictInfo节点列表,表达式或者是一个RestrictInfo节点的列表
//结果包括一次性的(启动)组件和每个评价的组件
cost_qual_eval_context context;
ListCell *l;
context.root = root;
context.total.startup = 0;
context.total.per_tuple = 0;
//不对最高级别的隐含ANDing计算代价
foreach(l, quals)
Node *qual = (Node *) lfirst(l);
cost_qual_eval_walker(qual, &context);
*cost = context.total;
创建排序计划节点
make_sort函数用于生成排序计划节点,函数返回一个类型为sort的结构