受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

62.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,
                double *indexPages);

前三个参数是输入参数:

root

规划器关于当前正在处理查询的信息。

path

当前正在考虑的索引访问路径。除开销和选择率字段外,其余字段都有效。

loop_count

在开销估计中应计入的索引扫描重复次数。当考虑在嵌套循环连接内部使用参数化扫描时,这个参数通常会大于 1。请注意,开销估计仍应只针对一次扫描;更大的 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。此外,在索引处理期间调用的任何比较操作符(特别是对 indexquals 本身的求值)都应计入适当倍数的 cpu_operator_cost

访问开销应包括与扫描索引本身有关的全部磁盘和 CPU 开销,但包括取出或处理由索引标识出的父表行的开销。

启动开销是整个扫描总开销中必须在开始取第一行之前先付出的那一部分。对大多数索引来说,这可以视为零;但启动开销较高的索引类型可能希望把它设为非零。

indexSelectivity 应设为在索引扫描期间将从父表中检索出的行的估计比例。对于有损查询,这个值通常会高于实际通过给定限定条件的行比例。

indexCorrelation 应设为索引顺序与表顺序之间的相关性(范围从 -1.0 到 1.0)。该值用于调整从父表取行开销的估计。

indexPages 应设为叶子页面数量。它会被用来估算并行索引扫描所需工作进程的数量。

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 中找到。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。