↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1
历史版本。 PostgreSQL 9.6 已结束支持。 2021-11-11. 请参阅 当前版本手册.

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

前三个参数是输入参数:

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。此外,在索引处理期间调用的任何比较操作符(特别是对 indexquals 本身的求值)都应计入适当倍数的 cpu_operator_cost。

访问代价应包括与扫描索引本身有关的全部磁盘和 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. 计算索引访问代价。一个通用估计器可能会这样做:

    /*
     * 通用假设是索引页面将按顺序读取,
     * 因此每页代价为 seq_page_cost,而非 random_page_cost。
     * 此外,还要计入在每个索引行上对 indexquals 求值的代价。
     * 假定所有代价都在扫描过程中逐步付出。
     */
    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 中找到。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.