PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

63.1. 索引的基本 API 结构 #

每个索引访问方法都由 pg_am 系统目录中的一行描述。 pg_am 条目 为索引访问方法指定一个名称和一个 处理器函数。 这些条目可以用 CREATE ACCESS METHOD 和 DROP ACCESS METHOD SQL 命令创建和删除。

索引访问方法处理器函数必须被声明为接受一个 internal 类型的单一参数,并返回伪类型 index_am_handler。 该参数只是一个哑值,其作用仅仅是防止处理器函数被 SQL 命令直接 调用。 函数的结果必须是一个 palloc'd 的 IndexAmRoutine 类型结构,其中包含核心 代码使用该索引访问方法所需知道的一切。 IndexAmRoutine 结构也称为该访问方法的 API 结构,包含指定该访问方法各种固定属性的 字段,例如它是否支持多列索引。 更重要的是,它包含指向该访问方法支持函数的指针,这些函数完成访问 索引的全部实际工作。 这些支持函数是普通 C 函数,在 SQL 层面不可见也不可调用。 支持函数在 Section 63.2 中描述。

IndexAmRoutine 结构定义如下:

typedef struct IndexAmRoutine
{
    NodeTag     type;

    /*
     * Total number of strategies (operators) by which we can traverse/search
     * this AM.  Zero if AM does not have a fixed set of strategy assignments.
     */
    uint16      amstrategies;
    /* total number of support functions that this AM uses */
    uint16      amsupport;
    /* opclass options support function number or 0 */
    uint16      amoptsprocnum;
    /* does AM support ORDER BY indexed column's value? */
    bool        amcanorder;
    /* does AM support ORDER BY result of an operator on indexed column? */
    bool        amcanorderbyop;
    /* does AM support hashing using API consistent with the hash AM? */
    bool        amcanhash;
    /* do operators within an opfamily have consistent equality semantics? */
    bool        amconsistentequality;
    /* do operators within an opfamily have consistent ordering semantics? */
    bool        amconsistentordering;
    /* does AM support backward scanning? */
    bool        amcanbackward;
    /* does AM support UNIQUE indexes? */
    bool        amcanunique;
    /* does AM support multi-column indexes? */
    bool        amcanmulticol;
    /* does AM require scans to have a constraint on the first index column? */
    bool        amoptionalkey;
    /* does AM handle ScalarArrayOpExpr quals? */
    bool        amsearcharray;
    /* does AM handle IS NULL/IS NOT NULL quals? */
    bool        amsearchnulls;
    /* can index storage data type differ from column data type? */
    bool        amstorage;
    /* can an index of this type be clustered on? */
    bool        amclusterable;
    /* does AM handle predicate locks? */
    bool        ampredlocks;
    /* does AM support parallel scan? */
    bool        amcanparallel;
    /* does AM support parallel build? */
    bool        amcanbuildparallel;
    /* does AM support columns included with clause INCLUDE? */
    bool        amcaninclude;
    /* does AM use maintenance_work_mem? */
    bool        amusemaintenanceworkmem;
    /* does AM summarize tuples, with at least all tuples in the block
     * summarized in one summary */
    bool        amsummarizing;
    /* OR of parallel vacuum flags */
    uint8       amparallelvacuumoptions;
    /* type of data stored in index, or InvalidOid if variable */
    Oid         amkeytype;

    /* interface functions */
    ambuild_function ambuild;
    ambuildempty_function ambuildempty;
    aminsert_function aminsert;
    aminsertcleanup_function aminsertcleanup;   /* can be NULL */
    ambulkdelete_function ambulkdelete;
    amvacuumcleanup_function amvacuumcleanup;
    amcanreturn_function amcanreturn;   /* can be NULL */
    amcostestimate_function amcostestimate;
    amgettreeheight_function amgettreeheight;   /* can be NULL */
    amoptions_function amoptions;
    amproperty_function amproperty;     /* can be NULL */
    ambuildphasename_function ambuildphasename;   /* can be NULL */
    amvalidate_function amvalidate;
    amadjustmembers_function amadjustmembers; /* can be NULL */
    ambeginscan_function ambeginscan;
    amrescan_function amrescan;
    amgettuple_function amgettuple;     /* can be NULL */
    amgetbitmap_function amgetbitmap;   /* can be NULL */
    amendscan_function amendscan;
    ammarkpos_function ammarkpos;       /* can be NULL */
    amrestrpos_function amrestrpos;     /* can be NULL */

    /* interface functions to support parallel index scans */
    amestimateparallelscan_function amestimateparallelscan;    /* can be NULL */
    aminitparallelscan_function aminitparallelscan;    /* can be NULL */
    amparallelrescan_function amparallelrescan;    /* can be NULL */

    /* interface functions to support planning */
    amtranslate_strategy_function amtranslatestrategy;  /* can be NULL */
    amtranslate_cmptype_function amtranslatecmptype;    /* can be NULL */
} IndexAmRoutine;

为了有用,一个索引访问方法还必须在 pg_opfamily、 pg_opclass、 pg_amop 和 pg_amproc 中定义一个或多个 操作符族和 操作符类。 这些条目让规划器能够确定哪些类型的查询条件可以与这种访问方法的索引 一起使用。 操作符族和类在 Section 36.16 中描述,它是阅读本章的 先修材料。

单个索引由把它描述为物理关系的 pg_class 条目,加上展示索引逻辑内容的 pg_index 条目定义 — 也就是说,由它拥有的索引列集合以及这些列的语义 (由相关联的操作符类捕获)定义。 索引列(键值)可以是底层表的简单列,也可以是基于表行的表达式。 索引访问方法通常对索引键值的来源没有兴趣(它总是被交给预先计算好的 键值),但它会对 pg_index 中的操作符类信息非常感兴趣。 这两个目录条目都可以作为传递给索引上所有操作的 Relation 数据结构的一部分被访问。

IndexAmRoutine 的一些标志字段有非显而易见的 含义。 amcanunique 的要求在 Section 63.5 中讨论。 amcanmulticol 标志断言该访问方法支持多 键列索引,而 amoptionalkey 断言它允许对第一个索引列 没有可索引限制子句的扫描。 当 amcanmulticol 为 false 时, amoptionalkey 实质上说明该访问方法是否 支持不带任何限制子句的完整索引扫描。 支持多个索引列的访问方法必须支持对第一列之后 的任何或所有列省略限制的扫描;但它们被允许要求对第一个索引列出现 某种限制,这是通过把 amoptionalkey 设为 false 来表示的。 索引 AM 把 amoptionalkey 设为 false 的一个原因 可能是它不为空值建立索引。 由于大多数可索引操作符是严格的,因此对空输入不可能返回真, 所以不为空值存储索引条目乍看之下很有吸引力: 反正索引扫描也永远不会返回它们。 然而,当索引扫描对某个索引列没有限制子句时,这个论证就失效了。 在实践中,这意味着拥有 amoptionalkey true 的索引必须为空值建立 索引,因为规划器可能决定在完全没有扫描键的情况下使用这样的索引。 一个相关的限制是,支持多个索引列的索引访问方法 必须支持为第一列之后的列中的空值建立索引, 因为规划器会假定该索引可以用于不限制这些列的查询。 例如,考虑 (a,b) 上的索引和带 WHERE a = 4 的查询。系统会假定该索引可用于搜索 a = 4 的行,如果索引省略了 b 为空的行,这就是错误的。 不过,省略第一个被索引列为空的行是可以的。 为空值建立索引的索引访问方法还可以设置 amsearchnulls,表示它支持 IS NULL 和 IS NOT NULL 子句 作为搜索条件。

amcaninclude 标志表示访问方法是否支持 “包含的”列,即它可以存储(而不处理)键列之外的额外 列。 前一段的要求只适用于键列。 特别地,amcanmulticol=false 与 amcaninclude=true 的组合是合理的:它意味着只能有一个键列,但还可以有被包含的 列。 此外,被包含的列必须被允许为空,独立于 amoptionalkey。

amsummarizing 标志表示访问方法是否对被 索引的元组进行汇总,汇总粒度至少为每块一次。 不指向单个元组而是指向块范围(例如 BRIN)的访问方法,可以允许 HOT 优化继续进行。 这不适用于索引谓词中引用的属性,更新这样的属性总是会使 HOT 失效。