54.6. 索引开销估计函数

系统给amcostestimate 一些描述可能的索引扫描的信息,包括一个 WHERE 子句和 ORDER BY 子句的列表,这个子句列表是系统认为可以被索引使用的东西。 它必须返回访问该索引的开销估计值以及 WHERE 子句的选择性(也就是说,在索引扫描期间检索的将被返回的数据行在父表中所占据的比例)。 对于简单的场合,几乎开销估计器的所有工作都可以通过调用优化器里面的标准过程完成; 有amcostestimate这个函数的目的是允许索引访问方法提供和索引类型相关的知识,这样也许可以改进标准的开销估计。

每个amcostestimate函数都必须有下面这样的签名:

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation);

头3个参数是输入:

root

规划器的有关正在被处理的查询的信息。

path

被考虑的索引访问路径。除了开销和选择性其他所有的域都是有效的。

loop_count

索引扫描的重复次数,这个应该被考虑到开销估算中。 当考虑到用于嵌套循环内部的参数化扫描时,通常它大于1。 注意开销估算仍然只是为了一次扫描;一个大的loop_count的意思在多次扫描间进行某些缓存可能是合适的。

后面四个参数是传递引用的输出:

*indexStartupCost

设置为索引启动处理的开销

*indexTotalCost

设置为索引处理的总开销

*indexSelectivity

设置为索引的选择性

*indexCorrelation

设置为索引扫描顺序和下层的表的顺序之间的相关有效性

请注意开销估计函数必须用 C 写,而不能用 SQL 或者任何可用的存储过程语言,因为它们必须访问规划器/优化器的内部数据结构。

索引访问开销应该以src/backend/optimizer/path/costsize.c 使用的单位进行计算:一个顺序磁盘块抓取开销是seq_page_cost, 一个非顺序抓取开销是random_page_cost, 而处理一个索引行的开销通常应该是cpu_index_tuple_cost。 另外,在任何索引处理期间调用的比较操作符,都应该增加一个数量为cpu_operator_cost 倍数的开销(特别是计算索引条件 indexquals 自己的时候)。

访问开销应该包括所有与扫描索引本身相关的磁盘和 CPU 开销,但是不包括检索或者处理索引标识出来的父表的行的开销。

"启动开销"是总扫描开销中的这样一部分:在开始抓取第一行之前,必须花掉的开销。 对于大多数索引,这个可以是零,但是那些启动开销很大的索引类型可能不能把它设置为零。

indexSelectivity应该设置成在索引扫描期间,父表中的行被选出来的部分的百分比。 在有损耗的查询的情况下,这个值通常比实际通过给出的查询条件的行所占的百分比要高。

indexCorrelation应该设置成索引顺序和表顺序之间的相关性(范围在 -1.0 到 1.0 之间)。 这个数值用于调整从父表中抓取行的开销估计。

loop_count大于1,返回的数值应当在每一次索引扫描之间平均。

开销估计

一个典型的开销估计器会像下面这样进行处理:

  1. 基于给出的查询条件,估计并返回父表中将被访问的行的百分比。 如果缺乏索引类型相关的知识,那么使用标准的优化器函数clauselist_selectivity():

    *indexSelectivity = clauselist_selectivity(root, path->indexquals,
                                               path->indexinfo->rel->relid,
                                               JOIN_INNER, NULL);
    
  2. 估计在扫描过程中将被访问的索引行数。对于许多索引类型,这个等于indexSelectivity乘以索引中的行数,但是可能更多。 (请注意,索引的总页面数和行数可以从path->indexinfo结构中获得。)

  3. 估计在扫描中将取出的索引页面数量。这个可能就是indexSelectivity乘以索引的总页面数。

  4. 计算索引访问开销。一个通用的估计器可以这么干:

    /*
     * 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;
    

    不过,上面没有考虑多次重复索引扫描中索引读取的开销分摊。

  5. 估计索引的相关性。对于简单的在单个字段上的有序索引,这个值可以从 pg_statistic 中检索。 如果相关性是未知,那么保守的估计是零(没有相关性)。

开销估计器函数的例子可以在src/backend/utils/adt/selfuncs.c里面找到。