amcostestimate 函数会收到描述一次可能的
索引扫描的信息,包括已被确定为可与该索引一起使用的 WHERE 和
ORDER BY 子句的列表。
它必须返回访问索引的代价的估算以及 WHERE 子句的选择性
(即在索引扫描期间将被取回的父表行的比例)。
对简单情形,代价估算器几乎全部工作都可以通过调用优化器中的标准
例程完成;拥有
amcostestimate 函数的意义在于允许索引访问
方法提供索引类型特定的知识,以防有可能改进标准估算。
每个 amcostestimate 函数必须具有签名:
void
amcostestimate (PlannerInfo *root,
IndexPath *path,
double loop_count,
Cost *indexStartupCost,
Cost *indexTotalCost,
Selectivity *indexSelectivity,
double *indexCorrelation,
double *indexPages);
前三个参数是输入:
root规划器关于正在处理的查询的信息。
path正在考虑的索引访问路径。 除代价和选择性值之外的所有字段都有效。
loop_count
应当计入代价估算的索引扫描重复次数。
当考虑用于嵌套循环连接内侧的参数化扫描时,这通常大于一。
注意,代价估算仍应针对仅仅一次扫描;
较大的 loop_count 意味着考虑跨多次扫描
的某些缓存效应可能是合适的。
后五个参数是按引用传递的输出:
*indexStartupCost设为索引启动处理的代价
*indexTotalCost设为索引处理的总代价
*indexSelectivity设为索引选择性
*indexCorrelation设为索引扫描顺序与底层表顺序之间的相关系数
*indexPages设为索引叶页面数量
注意,代价估算函数必须用 C 编写,不能用 SQL 或任何可用的过程 语言编写,因为它们必须访问规划器/优化器的内部数据结构。
索引访问代价应当使用
src/backend/optimizer/path/costsize.c
所用的参数计算:一次顺序磁盘块取回的代价为
seq_page_cost,一次非顺序取回的代价为
random_page_cost,处理一个索引行的代价通常应
取为
cpu_index_tuple_cost。
此外,对索引处理期间调用的任何比较操作符
(尤其是 indexqual 本身的求值),应当收取
cpu_operator_cost 的适当倍数。
访问代价应当包括与扫描索引本身相关的所有磁盘和 CPU 代价,但 不包括取回或处理由索引标识的父表行的 代价。
“启动代价”是总扫描代价中在我们能够开始取回第一行 之前必须支出的部分。 对大多数索引这可以取为零,但启动代价高的索引类型可能想把它设为 非零。
indexSelectivity 应当被设为在索引扫描期间
将被取回的父表行的估算比例。
对有损查询,这通常会高于实际通过给定限定条件的行的比例。
indexCorrelation 应当被设为索引顺序与表
顺序之间的相关性(介于
-1.0 和 1.0 之间)。
它用于调整从父表取回行的代价估算。
indexPages 应当被设为叶页面的数量。
它用于估算并行索引扫描的工作进程数量。
当 loop_count 大于一时,返回的数字应当是
该索引任何一次扫描的期望平均值。
Cost Estimation
一个典型的代价估算器会按如下步骤进行:
基于给定的限定条件估算并返回将被访问的父表行的比例。
在没有任何索引类型特定知识的情况下,使用标准优化器函数
clauselist_selectivity():
*indexSelectivity = clauselist_selectivity(root, path->indexquals,
path->indexinfo->rel->relid,
JOIN_INNER, NULL);
估算扫描期间将被访问的索引行数。
对许多索引类型,这等于
indexSelectivity 乘以索引中的行数,
但它可能更多。
(注意,索引以页面和行计的大小可以从
path->indexinfo 结构获得。)
估算扫描期间将被取回的索引页面数量。
这可能只是
indexSelectivity 乘以索引以页面计的
大小。
计算索引访问代价。 一个通用的估算器可能会这样做:
/*
* Our generic assumption is that the index pages will be read
* sequentially, so they cost seq_page_cost each, not random_page_cost.
* Also, we charge for evaluation of the indexquals at each index row.
* All the costs are assumed to be paid incrementally during the scan.
*/
cost_qual_eval(&index_qual_cost, path->indexquals, root);
*indexStartupCost = index_qual_cost.startup;
*indexTotalCost = seq_page_cost * numIndexPages +
(cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;
不过,上面的做法没有考虑把索引读取摊销到重复的索引扫描 之上。
估算索引相关性。 对单字段上的简单有序索引,可以从 pg_statistic 获取。 如果相关性未知,保守的估算值是零(无相关性)。
代价估算函数的示例可以在
src/backend/utils/adt/selfuncs.c 中找到。