受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: devel

Chapter 60. 索引访问方法接口定义

本章定义 PostgreSQL 核心系统与管理各个索引类型的索引访问方法之间的接口。除这里规定的内容之外,核心系统对索引一无所知,因此可以通过编写附加代码来开发全新的索引类型。

PostgreSQL 中所有索引在技术上都称为二级索引;也就是说,索引与它所描述的表文件在物理上是分离的。每个索引都存储为独立的物理关系,因此在 pg_class 目录中都有一个条目。索引的内容完全由其索引访问方法控制。实践中,所有索引访问方法都会把索引划分为标准大小的页面,以便使用常规的存储管理器和缓冲区管理器访问索引内容。(现有的所有索引访问方法还都使用 Section 68.6 中描述的标准页面布局,而且大多数还对索引元组头使用相同的格式;但这些决定并不是访问方法必须遵循的。)

索引本质上是从一些数据键值到行版本(元组)在索引父表中的元组标识符TID)的映射。一个 TID 由块号以及该块中的项号组成(见 Section 68.6)。这些信息足以从表中取出某个特定的行版本。索引并不直接知道在 MVCC 之下同一逻辑行可能会存在多个现存版本;对索引来说,每个元组都是一个独立对象,需要它自己的索引条目。因此,对一行的更新总会为该行创建全新的索引条目,即使键值并未改变也是如此。(HOT 元组是这一说法的例外;但索引同样不直接处理它们。)当死元组自身被回收时(通过清理),它们对应的索引条目也会被回收。

60.1. 索引的基本 API 结构 #

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

索引访问方法的处理器函数必须声明为接受一个 internal 类型的参数,并返回伪类型 index_am_handler。这个参数只是一个占位值,用来防止处理器函数被 SQL 命令直接调用。该函数的结果必须是一个通过 palloc 分配的 IndexAmRoutine 结构体,其中包含核心代码使用该索引访问方法所需的全部信息。IndexAmRoutine 结构体也称为该访问方法的API 结构,其中的字段指定了访问方法的各种固定属性,例如它是否支持多列索引。更重要的是,它包含该访问方法的支持函数指针,而这些支持函数完成了实际访问索引的全部工作。这些支持函数是普通 C 函数,在 SQL 层不可见也不可调用。支持函数将在 Section 60.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_opfamilypg_opclasspg_amoppg_amproc 中定义一个或多个操作符族操作符类。这些条目使规划器能够确定该访问方法的索引可以使用哪些查询限定条件。关于操作符族和操作符类的说明见 Section 36.16;阅读本章前应先掌握这些内容。

单个索引由两个系统目录条目共同定义:一个 pg_class 条目把它描述为一个物理关系,另一个 pg_index 条目给出索引的逻辑内容,也就是它包含哪些索引列,以及由相关操作符类定义的这些列的语义。索引列(键值)既可以是底层表的简单列,也可以是基于表行的表达式。索引访问方法通常并不关心索引键值来自何处(它拿到的总是预先计算好的键值),但会非常关心 pg_index 中的操作符类信息。这两个目录条目都可以作为传递给索引上所有操作的 Relation 数据结构的一部分来访问。

IndexAmRoutine 的某些标志字段还有一些不那么直观的含义。amcanunique 的要求见 Section 60.5amcanmulticol 标志表明该访问方法支持多键列索引,而 amoptionalkey 表明它允许在第一索引列上没有给出可索引限制子句时进行扫描。当 amcanmulticol 为假时,amoptionalkey 实质上表示该访问方法是否支持没有任何限制子句的全索引扫描。支持多个索引列的访问方法必须支持在第一列之后省略任意一个或全部列限制条件的扫描;但它们可以要求第一索引列必须出现某种限制条件,这通过把 amoptionalkey 设为假来表示。索引 AM 可能把 amoptionalkey 设为假的一个原因,是它不索引 NULL 值。由于大多数可索引操作符都是严格的(strict),因此对 NULL 输入不可能返回 true,所以乍看之下不存储 NULL 值的索引条目似乎很有吸引力:无论如何,这些条目似乎都不可能被索引扫描返回。然而,当某个索引扫描对给定索引列没有限制子句时,这个论证就不成立了。实践中这意味着,若索引把 amoptionalkey 设为真,就必须索引 NULL 值,因为规划器可能决定在完全没有扫描键的情况下使用这种索引。与此相关的另一个限制是,支持多个索引列的索引访问方法必须支持对第一列之后各列中的 NULL 值建立索引,因为规划器会假定该索引可用于不限制这些列的查询。例如,考虑一个在 (a,b) 上的索引以及查询 WHERE a = 4。系统会假定该索引可用于扫描满足 a = 4 的行;如果索引省略了 b 为 NULL 的行,这个假定就是错误的。不过,省略第一索引列为 NULL 的行是可以的。对 NULL 值建立索引的索引访问方法还可以设置 amsearchnulls,表示它支持把 IS NULLIS NOT NULL 子句作为搜索条件。

amcaninclude 标志指示该访问方法是否支持包含列,也就是说,它是否能在键列之外额外存储其他列而不对其进行处理。上一段中的要求只适用于键列。特别是,amcanmulticol=falseamcaninclude=true 的组合是合理的:这表示只能有一个键列,但仍然可以有包含列。此外,不论 amoptionalkey 的取值如何,包含列都必须允许为 NULL。

amsummarizing 标志指示该访问方法是否会对被索引元组做提要化,并且提要粒度至少达到每个块一个提要。不指向单个元组,而是指向块范围(例如 BRIN)的访问方法,可能允许继续使用 HOT 优化。这不适用于索引谓词中引用的属性,对这类属性的更新总会禁用 HOT

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。