1.1.1.1 索引扫描的代价:cost_index
函数功能:
某个索引获取元组的花费。不同类型的索引,有着不同的基本属性值的(如选择率、索引相关度等)花费估算函数(在pg_am.h的amcostestimate列上定义)。索引扫描代价,在PostgreSQL中分为普通索引的扫描代价、只读索引扫描的代价。只读索引扫描因为不需要访问数据文件,所以可以减少花费。不管是哪种方式,基本原理都遵从代价估算模型“总代价 = I/O代价 + CPU代价”;局部差别,只是根据实际的扫描过程所做的工作,对每个主要步骤做花费计算。索引扫描有别于顺序扫描,是增加了对索引文件的访问,这项花费需要计算。
代码分析:
void
cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
{......
if(!enable_indexscan)
startup_cost+= disable_cost;
//每一类索引都有自己的花费基本值估算函数,如btree索引的花费估算函数是btcostestimate函数。对于不同的表达式,
OidFunctionCall7(index->amcostestimate,
PointerGetDatum(root),
PointerGetDatum(path),
Float8GetDatum(loop_count),
PointerGetDatum(&indexStartupCost), //索引启动花费
PointerGetDatum(&indexTotalCost), //索引总花费
PointerGetDatum(&indexSelectivity),//索引选择率
PointerGetDatum(&indexCorrelation)); //索引相关度
......
startup_cost+= indexStartupCost;
run_cost+= indexTotalCost - indexStartupCost;
//估算表元组数:基于索引的选择率与元组个数的乘积是能取到的元组个数
tuples_fetched= clamp_row_est(indexSelectivity * baserel->tuples);
//从表空间取数据页的IO花费:随机读取的花费、顺序读取的花费
get_tablespace_page_costs(baserel->reltablespace,
&spc_random_page_cost,
&spc_seq_page_cost);
//根据索引和索引使用次数,在区分“索引扫描”和“只索引扫描”方式下,计算索引页的IO花费
if(loop_count > 1)
{
//最大IO计算方式:
//计算依据:元组数、循环次数、基表页面数、索引页面数
pages_fetched= index_pages_fetched(tuples_fetched * loop_count,
baserel->pages,(double) index->pages, root);
//如果是“只索引扫描”,则重新计算页面获取数(因为不需要做表的扫描,只扫描索引的页面即可)
if(indexonly)
pages_fetched= ceil(pages_fetched * (1.0 - baserel->allvisfrac));
max_IO_cost= (pages_fetched * spc_random_page_cost) / loop_count;
//最小IO计算方式:
//考虑了索引存在选择率的问题
//计算依据:索引选择率、元组数、循环次数、基表页面数、索引页面数
pages_fetched= ceil(indexSelectivity* (double) baserel->pages);
pages_fetched= index_pages_fetched(pages_fetched * loop_count,
baserel->pages,(double) index->pages, root);
//如果是“只索引扫描”,则重新计算页面获取数
if(indexonly)
pages_fetched= ceil(pages_fetched * (1.0 - baserel->allvisfrac));
min_IO_cost= (pages_fetched * spc_random_page_cost) / loop_count;
}
else//道理同上,只是不需要考虑循环次数
{……}
csquared= indexCorrelation * indexCorrelation;
run_cost+= max_IO_cost + csquared * (min_IO_cost - max_IO_cost);
//估算每个元组的CPU花费赋值给qpqual_cost
cost_qual_eval(&qpqual_cost,list_difference_ptr(allclauses, path->indexquals), root);
//计算每个元组的CPU花费
startup_cost+= qpqual_cost.startup;
cpu_per_tuple= cpu_tuple_cost + qpqual_cost.per_tuple;
run_cost+= cpu_per_tuple * tuples_fetched;//计算获取所有元组的CPU花费
path->path.startup_cost= startup_cost;
path->path.total_cost= startup_cost + run_cost;
//最后的总的花费是:索引和基表限制条件的启动花费+总的IO花费(主要是索引页的IO花费)+总的CPU花费(元组)
}
调用关系图解:

1. cost_index函数被create_index_path函数调用,完成索引扫描的花费估算;
2. cost_index函数被reparameterize_path函数调用,完成参数化路径的索引扫描的花费估算。
1.1.1.2 Tid扫描的代价估算:cost_tidscan
函数功能:
计算一个关系使用Tid扫描方式的花费。
代码分析:
Void cost_tidscan(Path*path, PlannerInfo *root, RelOptInfo *baserel, List *tidquals)
{
……
//计算期望得到的元组个数(ntuples),分三种情况处理,tid分别是ScalarArrayOpExpr、CurrentOfExpr、CTID= something这三种格式
……
//计算Tid条件的花费(值计算一次,其他的限制条件的花费需要为每个元组都计算一次)
cost_qual_eval(&tid_qual_cost,tidquals, root);
//从表空间取数据页的IO花费:随机读取的花费
get_tablespace_page_costs(baserel->reltablespace, &spc_random_page_cost, NULL);
//计算元组获取的IO花费
run_cost+= spc_random_page_cost * ntuples;
//计算元组获取的CPU花费
startup_cost+= baserel->baserestrictcost.startup +
tid_qual_cost.per_tuple;
cpu_per_tuple= cpu_tuple_cost + baserel->baserestrictcost.per_tuple -
tid_qual_cost.per_tuple;
run_cost+= cpu_per_tuple * ntuples;
path->startup_cost= startup_cost;
path->total_cost= startup_cost + run_cost;
//最后的总的花费是:基表限制条件的启动花费(包括tid条件花费)+总的IO花费+总的CPU花费
}
调用关系图解:

1. cost_tidscan函数被create_tidscan_path函数调用,直至被set_plain_rel_pathlist函数调用,用以完成单表tid扫描的花费估算。