2024 年 9 月 26 日: PostgreSQL 17 发布!
支持版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不受支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1

62.6. 索引成本估算函数 #

The 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。此外,应该为索引处理期间调用的任何比较运算符(特别是索引限定符本身的评估)收取适当的 cpu_operator_cost 倍数。

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

The 启动成本 是总扫描成本的一部分,必须在我们开始获取第一行之前花费。对于大多数索引,这可以取为零,但是启动成本高的索引类型可能希望将其设置为非零。

The indexSelectivity 应设置为在索引扫描期间将检索到的父表行的估计分数。在有损查询的情况下,这通常高于实际通过给定限定条件的行的分数。

The indexCorrelation 应设置为索引顺序与表顺序之间的相关性(介于 -1.0 和 1.0 之间)。这用于调整从父表中获取行的成本估计。

The 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 中可以找到成本估算器函数的示例。

提交更正

如果您在文档中发现任何不正确的内容,与您对特定功能的体验不符或需要进一步澄清,请使用 此表格 报告文档问题。