60.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);

前三个参数是 Importing:

  • root

    • 计划者有关正在处理的查询的信息。
  • path

    • 正在考虑的索引访问路径。除费用和选择性值以外的所有字段均有效。
  • loop_count

    • 索引扫描的重复次数应计入成本估算中。当考虑在嵌套循环连接内部使用参数化扫描时,该值通常大于一。请注意,成本估算仍应仅进行一次扫描。 * loop_count *较大表示在多次扫描中允许一些缓存效果可能是适当的。

最后五个参数是按引用传递输出:

  • *indexStartupCost

    • 设置为索引启动处理的成本
  • *indexTotalCost

    • 设置为索引处理的总成本
  • *indexSelectivity

    • 设置为索引选择性
  • *indexCorrelation

    • 设置为索引扫描 Sequences 和基础表 Sequences 之间的相关系数
  • *indexPages

    • 设置为索引叶页数

请注意,成本估算功能必须用 C 编写,而不用 SQL 或任何可用的过程语言编写,因为它们必须访问计划程序/优化程序的内部数据结构。

索引访问成本应使用src/backend/optimizer/path/costsize.c所使用的参数来计算:Sequences 磁盘块获取的成本为seq_page_cost,非 Sequences 磁盘获取的成本为random_page_cost,处理一个索引行的成本通常应为cpu_index_tuple_cost。此外,对于索引处理期间调用的任何比较运算符(特别是对 indexquals 的求值),应收取cpu_operator_cost的适当倍数。

访问成本应包括与扫描索引本身相关的所有磁盘和 CPU 成本,但不包括检索或处理由索引标识的父表行的成本。

“启动成本”是总扫描成本中必须首先花费的一部分,我们才能开始获取第一行。对于大多数索引,可以将其视为零,但是启动成本较高的索引类型可能希望将其设置为非零。

  • indexSelectivity *应该设置为将在索引扫描期间检索的父表行的估计分数。在有损查询的情况下,这通常会高于实际通过给定条件的行所占的比例。

  • indexCorrelation *应该设置为索引 Sequences 和表 Sequences 之间的相关性(介于-1.0 和 1.0 之间)。这用于调整从父表获取行的成本估算。

  • indexPages *应该设置为叶子页的数量。这用于估计并行索引扫描的工作程序数量。

当* loop_count *大于 1 时,返回的数字应该是对索引的任何一次扫描所期望的平均值。

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