This commit is contained in:
yangke1125 2023-10-04 20:29:47 +08:00
parent 3c36671432
commit 0434074e45
1 changed files with 150 additions and 143 deletions

View File

@ -4828,25 +4828,40 @@ Cost cost_rescan_material(double rows, int width, OpMemInfo* mem_info, bool vect
* the run_cost charge in cost_sort, and also see comments in
* cost_material before you change it.)
*/
double local_rows = rows / dop;
Cost run_cost = u_sess->attr.attr_sql.cpu_operator_cost * local_rows;
double nbytes = relation_byte_size(local_rows, width, vectorized, true, false);
long work_mem_bytes = u_sess->opt_cxt.op_work_mem * 1024L / dop;
/* It will spill, so account for re-read cost */
double npages = ceil(nbytes / BLCKSZ);
double disk_cost = u_sess->attr.attr_sql.seq_page_cost * npages;
// 计算每个执行节点的本地行数
double local_rows = rows / dop;
if (nbytes > work_mem_bytes) {
run_cost += disk_cost;
}
if (mem_info != NULL) {
mem_info->opMem = u_sess->opt_cxt.op_work_mem;
mem_info->maxMem = nbytes / 1024L * dop;
mem_info->minMem = mem_info->maxMem / SORT_MAX_DISK_SIZE;
mem_info->regressCost = disk_cost;
}
// 计算运行成本,考虑每个执行节点的本地行数和 CPU 运算成本
Cost run_cost = u_sess->attr.attr_sql.cpu_operator_cost * local_rows;
// 计算每个执行节点需要的字节数,考虑本地行数、宽度、是否矢量化等因素
double nbytes = relation_byte_size(local_rows, width, vectorized, true, false);
// 计算每个执行节点的工作内存字节数,根据工作内存配置和并行度进行分配
long work_mem_bytes = u_sess->opt_cxt.op_work_mem * 1024L / dop;
// 计算数据需要的页数,以 BLCKSZ 为单位
double npages = ceil(nbytes / BLCKSZ);
// 计算磁盘成本,考虑数据页数和磁盘顺序扫描成本
double disk_cost = u_sess->attr.attr_sql.seq_page_cost * npages;
// 如果数据字节数超过工作内存限制,考虑磁盘读取成本
if (nbytes > work_mem_bytes) {
run_cost += disk_cost;
}
// 如果传入了内存信息结构体,则更新内存信息
if (mem_info != NULL) {
mem_info->opMem = u_sess->opt_cxt.op_work_mem;
mem_info->maxMem = nbytes / 1024L * dop;
mem_info->minMem = mem_info->maxMem / SORT_MAX_DISK_SIZE;
mem_info->regressCost = disk_cost;
}
// 返回运行成本
return run_cost;
return run_cost;
}
#ifdef PGXC
@ -4921,42 +4936,44 @@ static bool cost_qual_eval_walker(Node* node, cost_qual_eval_context* context)
* cost more than once. If the clause's cost hasn't been computed yet,
* the field's startup value will contain -1.
*/
if (IsA(node, RestrictInfo)) {
RestrictInfo* rinfo = (RestrictInfo*)node;
// 检查传入的节点是否为 RestrictInfo 类型
if (IsA(node, RestrictInfo)) {
RestrictInfo* rinfo = (RestrictInfo*)node;
if (rinfo->eval_cost.startup < 0) {
cost_qual_eval_context locContext;
// 如果评估成本中的 startup 值小于 0则需要计算
if (rinfo->eval_cost.startup < 0) {
cost_qual_eval_context locContext;
locContext.root = context->root;
locContext.total.startup = 0;
// 初始化评估上下文,设置根节点和成本初始值
locContext.root = context->root;
locContext.total.startup = 0;
locContext.total.per_tuple = 0;
// 如果存在 OR 子句,计算 OR 子句中的表达式成本
if (rinfo->orclause)
(void)cost_qual_eval_walker((Node*)rinfo->orclause, &locContext);
else
(void)cost_qual_eval_walker((Node*)rinfo->clause, &locContext);
// 如果表达式被标记为伪常量,将 startup 成本添加到总成本中
if (rinfo->pseudoconstant) {
locContext.total.startup += locContext.total.per_tuple;
locContext.total.per_tuple = 0;
/*
* For an OR clause, recurse into the marked-up tree so that we
* set the eval_cost for contained RestrictInfos too.
*/
if (rinfo->orclause)
(void)cost_qual_eval_walker((Node*)rinfo->orclause, &locContext);
else
(void)cost_qual_eval_walker((Node*)rinfo->clause, &locContext);
/*
* If the RestrictInfo is marked pseudoconstant, it will be tested
* only once, so treat its cost as all startup cost.
*/
if (rinfo->pseudoconstant) {
/* count one execution during startup */
locContext.total.startup += locContext.total.per_tuple;
locContext.total.per_tuple = 0;
}
rinfo->eval_cost = locContext.total;
}
context->total.startup += rinfo->eval_cost.startup;
context->total.per_tuple += rinfo->eval_cost.per_tuple;
/* do NOT recurse into children */
return false;
// 将计算得到的成本赋值给 RestrictInfo 结构体
rinfo->eval_cost = locContext.total;
}
// 将 RestrictInfo 的评估成本合并到上下文中的总成本中
context->total.startup += rinfo->eval_cost.startup;
context->total.per_tuple += rinfo->eval_cost.per_tuple;
// 返回 false 表示不需要继续递归处理子节点
return false;
}
/*
* For each operator or function node in the given tree, we charge the
* estimated execution cost given by pg_proc.procost (remember to multiply
@ -4979,103 +4996,80 @@ static bool cost_qual_eval_walker(Node* node, cost_qual_eval_context* context)
* moreover, since our rowcount estimates for functions tend to be pretty
* phony, the results would also be pretty phony.
*/
if (IsA(node, FuncExpr)) {
context->total.per_tuple += get_func_cost(((FuncExpr*)node)->funcid) * u_sess->attr.attr_sql.cpu_operator_cost;
} else if (IsA(node, OpExpr) || IsA(node, DistinctExpr) || IsA(node, NullIfExpr)) {
/* rely on struct equivalence to treat these all alike */
set_opfuncid((OpExpr*)node);
context->total.per_tuple += get_func_cost(((OpExpr*)node)->opfuncid) * u_sess->attr.attr_sql.cpu_operator_cost;
} else if (IsA(node, ScalarArrayOpExpr)) {
/*
* Estimate that the operator will be applied to about half of the
* array elements before the answer is determined.
*/
ScalarArrayOpExpr* saop = (ScalarArrayOpExpr*)node;
Node* arraynode = (Node*)lsecond(saop->args);
// 检查传入的节点是否为 FuncExpr 类型
if (IsA(node, FuncExpr)) {
// 如果是 FuncExpr 类型,将函数的评估成本添加到总成本中
context->total.per_tuple += get_func_cost(((FuncExpr*)node)->funcid) * u_sess->attr.attr_sql.cpu_operator_cost;
} else if (IsA(node, OpExpr) || IsA(node, DistinctExpr) || IsA(node, NullIfExpr)) {
// 如果是 OpExpr、DistinctExpr 或 NullIfExpr 类型,设置操作符的函数 ID 并添加其评估成本到总成本中
set_opfuncid((OpExpr*)node);
context->total.per_tuple += get_func_cost(((OpExpr*)node)->opfuncid) * u_sess->attr.attr_sql.cpu_operator_cost;
} else if (IsA(node, ScalarArrayOpExpr)) {
// 如果是 ScalarArrayOpExpr 类型,设置标量数组操作符的函数 ID 并添加其评估成本到总成本中
ScalarArrayOpExpr* saop = (ScalarArrayOpExpr*)node;
Node* arraynode = (Node*)lsecond(saop->args);
set_sa_opfuncid(saop);
context->total.per_tuple += get_func_cost(saop->opfuncid) * u_sess->attr.attr_sql.cpu_operator_cost *
estimate_array_length(arraynode) * 0.5;
} else if (IsA(node, Aggref) || IsA(node, WindowFunc)) {
/*
* Aggref and WindowFunc nodes are (and should be) treated like Vars,
* ie, zero execution cost in the current model, because they behave
* essentially like Vars in execQual.c. We disregard the costs of
* their input expressions for the same reason. The actual execution
* costs of the aggregate/window functions and their arguments have to
* be factored into plan-node-specific costing of the Agg or WindowAgg
* plan node.
*/
return false; /* don't recurse into children */
} else if (IsA(node, CoerceViaIO)) {
CoerceViaIO* iocoerce = (CoerceViaIO*)node;
Oid iofunc;
Oid typioparam;
bool typisvarlena = false;
set_sa_opfuncid(saop);
context->total.per_tuple += get_func_cost(saop->opfuncid) * u_sess->attr.attr_sql.cpu_operator_cost *
estimate_array_length(arraynode) * 0.5;
} else if (IsA(node, Aggref) || IsA(node, WindowFunc)) {
// 如果是 Aggref 或 WindowFunc 类型,返回 false 表示不需要继续处理子节点
return false;
} else if (IsA(node, CoerceViaIO)) {
// 如果是 CoerceViaIO 类型,计算输入和输出函数的评估成本并添加到总成本中
CoerceViaIO* iocoerce = (CoerceViaIO*)node;
Oid iofunc;
Oid typioparam;
bool typisvarlena = false;
/* check the result type's input function */
getTypeInputInfo(iocoerce->resulttype, &iofunc, &typioparam);
context->total.per_tuple += get_func_cost(iofunc) * u_sess->attr.attr_sql.cpu_operator_cost;
/* check the input type's output function */
getTypeOutputInfo(exprType((Node*)iocoerce->arg), &iofunc, &typisvarlena);
context->total.per_tuple += get_func_cost(iofunc) * u_sess->attr.attr_sql.cpu_operator_cost;
} else if (IsA(node, ArrayCoerceExpr)) {
ArrayCoerceExpr* acoerce = (ArrayCoerceExpr*)node;
Node* arraynode = (Node*)acoerce->arg;
getTypeInputInfo(iocoerce->resulttype, &iofunc, &typioparam);
context->total.per_tuple += get_func_cost(iofunc) * u_sess->attr.attr_sql.cpu_operator_cost;
if (OidIsValid(acoerce->elemfuncid))
context->total.per_tuple += get_func_cost(acoerce->elemfuncid) * u_sess->attr.attr_sql.cpu_operator_cost *
estimate_array_length(arraynode);
} else if (IsA(node, RowCompareExpr)) {
/* Conservatively assume we will check all the columns */
RowCompareExpr* rcexpr = (RowCompareExpr*)node;
ListCell* lc = NULL;
getTypeOutputInfo(exprType((Node*)iocoerce->arg), &iofunc, &typisvarlena);
context->total.per_tuple += get_func_cost(iofunc) * u_sess->attr.attr_sql.cpu_operator_cost;
} else if (IsA(node, ArrayCoerceExpr)) {
// 如果是 ArrayCoerceExpr 类型,根据元素函数 ID 计算评估成本并添加到总成本中
ArrayCoerceExpr* acoerce = (ArrayCoerceExpr*)node;
Node* arraynode = (Node*)acoerce->arg;
foreach (lc, rcexpr->opnos) {
Oid opid = lfirst_oid(lc);
if (OidIsValid(acoerce->elemfuncid))
context->total.per_tuple += get_func_cost(acoerce->elemfuncid) * u_sess->attr.attr_sql.cpu_operator_cost *
estimate_array_length(arraynode);
} else if (IsA(node, RowCompareExpr)) {
// 如果是 RowCompareExpr 类型,计算操作符函数的评估成本并添加到总成本中
RowCompareExpr* rcexpr = (RowCompareExpr*)node;
ListCell* lc = NULL;
context->total.per_tuple += get_func_cost(get_opcode(opid)) * u_sess->attr.attr_sql.cpu_operator_cost;
}
} else if (IsA(node, CurrentOfExpr)) {
/* Report high cost to prevent selection of anything but TID scan */
context->total.startup += g_instance.cost_cxt.disable_cost;
} else if (IsA(node, SubLink)) {
/* This routine should not be applied to un-planned expressions */
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
errmsg("cannot handle unplanned sub-select when costing quals")));
} else if (IsA(node, SubPlan)) {
/*
* A subplan node in an expression typically indicates that the
* subplan will be executed on each evaluation, so charge accordingly.
* (Sub-selects that can be executed as InitPlans have already been
* removed from the expression.)
*/
SubPlan* subplan = (SubPlan*)node;
foreach (lc, rcexpr->opnos) {
Oid opid = lfirst_oid(lc);
context->total.startup += subplan->startup_cost;
context->total.per_tuple += subplan->per_call_cost;
/*
* We don't want to recurse into the testexpr, because it was already
* counted in the SubPlan node's costs. So we're done.
*/
return false;
} else if (IsA(node, AlternativeSubPlan)) {
/*
* Arbitrarily use the first alternative plan for costing. (We should
* certainly only include one alternative, and we don't yet have
* enough information to know which one the executor is most likely to
* use.)
*/
AlternativeSubPlan* asplan = (AlternativeSubPlan*)node;
return cost_qual_eval_walker((Node*)linitial(asplan->subplans), context);
context->total.per_tuple += get_func_cost(get_opcode(opid)) * u_sess->attr.attr_sql.cpu_operator_cost;
}
} else if (IsA(node, CurrentOfExpr)) {
// 如果是 CurrentOfExpr 类型,添加禁用成本到启动成本中
context->total.startup += g_instance.cost_cxt.disable_cost;
} else if (IsA(node, SubLink)) {
// 如果是 SubLink 类型,报错,因为无法处理未计划的子查询
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE),
errmsg("cannot handle unplanned sub-select when costing quals")));
} else if (IsA(node, SubPlan)) {
// 如果是 SubPlan 类型,将子查询的启动成本和每次调用成本添加到总成本中
SubPlan* subplan = (SubPlan*)node;
/* recurse into children */
return expression_tree_walker(node, (bool (*)())cost_qual_eval_walker, (void*)context);
context->total.startup += subplan->startup_cost;
context->total.per_tuple += subplan->per_call_cost;
// 返回 false 表示不需要继续递归处理子节点
return false;
} else if (IsA(node, AlternativeSubPlan)) {
// 如果是 AlternativeSubPlan 类型,继续处理第一个替代子计划节点
return cost_qual_eval_walker((Node*)linitial(asplan->subplans), context);
}
// 继续递归处理节点的子节点
return expression_tree_walker(node, (bool (*)())cost_qual_eval_walker, (void*)context);
}
/*
@ -5090,18 +5084,23 @@ static bool cost_qual_eval_walker(Node* node, cost_qual_eval_context* context)
* some of the quals. We assume baserestrictcost was previously set
* by set_baserel_size_estimates().
*/
// 这是一个名为 get_restriction_qual_cost 的静态函数,计算限制条件的成本
static void get_restriction_qual_cost(
PlannerInfo* root, RelOptInfo* baserel, ParamPathInfo* param_info, QualCost* qpqual_cost)
{
// 如果 param_info 不为空,则计算 param_info 中的限制条件成本
if (param_info != NULL) {
/* Include costs of pushed-down clauses */
// 调用 cost_qual_eval 函数计算限制条件的成本,并存储在 qpqual_cost 中
cost_qual_eval(qpqual_cost, param_info->ppi_clauses, root);
// 将基本关系的限制条件成本加到 qpqual_cost 中
qpqual_cost->startup += baserel->baserestrictcost.startup;
qpqual_cost->per_tuple += baserel->baserestrictcost.per_tuple;
} else
} else {
// 如果 param_info 为空,则直接将基本关系的限制条件成本赋值给 qpqual_cost
*qpqual_cost = baserel->baserestrictcost;
}
}
/*
* compute_semi_anti_join_factors
@ -5222,31 +5221,39 @@ void compute_semi_anti_join_factors(PlannerInfo* root, RelOptInfo* outerrel, Rel
* unmatched outer tuple is cheap to process, whereas otherwise it's probably
* expensive.
*/
// 这是一个名为 has_indexed_join_quals 的函数,检查是否有索引关联的连接条件
bool has_indexed_join_quals(NestPath* joinpath)
{
// 获取连接路径的关联关系 ID 集合
Relids joinrelids = joinpath->path.parent->relids;
// 获取连接路径的内部路径
Path* innerpath = joinpath->innerjoinpath;
// 用于存储索引条件的列表
List* indexclauses = NIL;
// 标志,表示是否找到至少一个索引条件
bool found_one = false;
// 用于遍历列表的迭代器
ListCell* lc = NULL;
/* If join still has quals to evaluate, it's not fast */
// 如果连接路径有连接限制条件,则返回 false
if (joinpath->joinrestrictinfo != NIL)
return false;
/* Nor if the inner path isn't parameterized at all */
// 如果内部路径没有 param_info则返回 false
if (innerpath->param_info == NULL)
return false;
/* Find the indexclauses list for the inner scan */
// 根据内部路径的类型,获取相应的索引条件列表
switch (innerpath->pathtype) {
case T_IndexScan:
case T_IndexOnlyScan:
indexclauses = ((IndexPath*)innerpath)->indexclauses;
break;
case T_BitmapHeapScan: {
/* Accept only a simple bitmap scan, not AND/OR cases */
// 如果内部路径是 BitmapHeapScan则获取其 bitmapqual
Path* bmqual = ((BitmapHeapPath*)innerpath)->bitmapqual;
// 如果 bitmapqual 是 IndexPath则获取其索引条件列表
if (IsA(bmqual, IndexPath))
indexclauses = ((IndexPath*)bmqual)->indexclauses;
else