Chapter 49. 索引开销计算函数

作者: 由 Tom Lane() 写于 2000-01-24.

注意: 这些内容最后必须成为关于书写新的索引访问方式的更大的一章的一部分.

每种索引访问模式都必须提供一个用于规划器/优化器的开销计算函数. 这个函数的过程 OID 在访问模式在 pg_am 里的 记录的 amcostestimate 字段里给出.

注意: PostgreSQL 7.0 以前, 使用的是另外一种注册与索引相关的开销计算函数的模式.

amcostestimate 函数收到一列 WHERE 子句,这些子句被认为对索引是有用的. 这个函数本身必须返回计算出来的访问索引的开销和 WHERE 子句的选择性( 也就是说,在索引扫描过程中主表行中要被捡索出来的部分). 对于简单的情况, 几乎所有开销计算器的工作都可以通过调用优化器里标准的过程来完成, 需要一个 amcostestimate 函数的原因是允许索引访问模式提供一些索引类型相关的信息, 这样就有可能改进标准的计算(预计).

每个 amcostestimate 函数都必须有下面的名字:

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

前面四个参数是输入:

root

被处理的查询.

rel

索引所处的关系(表).

index

索引本身.

indexQuals

索引条件子句列表(隐含地 AND);一个 NIL 列表表明没有可用的条件.

最后四个参数是通过引用传递的输出:

*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,
                                               lfirsti(rel->relids));
         

  2. 计算(估计)在扫描过程中将要被访问的索引记录数. 对于许多索引类型,这个数字等于 indexSelectivity 乘以索引里面的记录数量, 但是它可以更多. (请注意索引在页面里的大小和记录可以从结构 IndexOptInfo 里获得.)

  3. 计算(估计)在扫描过程中将要被检索出的索引页面数. 这个数字可以只是 indexSelectivity 乘以以页面数计算的索引的大小.

  4. 计算索引访问开销.一个常见的计算器可以这样做:

        /*
         * (我们一般性的假设是索引页面将被顺序读入,
         *  因此它们每个的开销为1.0,没有 random_page_cose.
         *  同样,我们计算每条索引记录的索引条件的开销.
         *  所有开销都假设是在扫描过程中逐步递增的.)
         *
         * 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 tuple.
         * All the costs are assumed to be paid incrementally during the scan.
         */
        cost_qual_eval(&index_qual_cost, indexQuals);
        *indexStartupCost = 0;
        *indexTotalCost = numIndexPages +
            (cpu_index_tuple_cost + index_qual_cost.per_tuple) * numIndexTuples;
         

  5. 计算索引相关性.对于在一个字段上的简单排序的索引,这个它可以从 pg_statistic 中检索出来.如果相关性未知,保守的估计是零(不相关).

开销计算器的例子可以在 src/backend/utils/adt/selfuncs.c 找到.

通常,一个 amcostestimate 函数的 pg_proc 记录会应该显示八个参数,所有参数都声明为 internal (因为它们 的类型都不是 SQL 知道的类型),并且它们的返回类型是 void