48.6. 索引开销估计函数

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

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

void
amcostestimate (PlannerInfo *root,
                IndexOptInfo *index,
                List *indexQuals,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation);

头四个参数是输入:

root

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

index

在考虑使用的索引。

indexQuals

索引条件子句的列表(隐含是 AND 的); 如果是 NIL 列表(空列表)就表示没有可用的条件。 请注意这个列表包含表达式树,而不是 ScanKey (扫描键字)。

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

*indexStartupCost

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

*indexTotalCost

设置为索引处理的总开销

*indexSelectivity

设置为索引的选择型

*indexCorrelation

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

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

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

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

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

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

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

开销估计

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

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

    *indexSelectivity = clauselist_selectivity(root, indexQuals,
                                               index->rel->relid, JOIN_INNER);

  2. 估计在扫描过程中将被访问的索引行数。对于许多索引类型,这个等于 indexSelectivity 乘以索引中的行数, 但是可能更多。(请注意,页面中的索引尺寸和行数可以从 IndexOptInfo 结构中获得。)

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

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

        /*
         * Our generic assumption is that the index pages will be read
         * sequentially, so they have cost 1.0 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.
         * 一般来说,我们假设索引页面都会顺序读取,所以它们每个的开销是 1.0,而不是 random_page_cost。
         * 同样,我们应该给每次索引行的 indexquals 计算开销。
         * 所有开销都被认为是在扫描过程中逐步增付的。
         */
        cost_qual_eval(&index_qual_cost, indexQuals);
        *indexStartupCost = index_qual_cost.startup;
        *indexTotalCost = numIndexPages +
            (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;

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

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