61.1. 索引的基本 API 结构

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

一个索引访问方法的处理器函数必须被声明为接受单一的类型为internal类型的参数并且返回伪类型index_am_handler。该参数是一个无用值,它只是被用来防止从 SQL 命令直接调用处理器函数。该函数的结果必须是一个已经 palloc 过的IndexAmRoutine类型结构,它包含核心代码使用该索引访问方法所需的所有信息。IndexAmRoutine结构(也被称为访问方法的API 结构)中的域指定了该访问方法的各种固定性质,例如它是否支持多列索引。更重要的是,它包含用于该访问方法的支持函数的指针,这些函数会完成真正访问索引的工作。这些支持函数是纯 C 函数,并且在 SQL 层面不可见也不可调用。支持函数在第 61.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;
    /* 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 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 columns included with clause INCLUDE? */
    bool        amcaninclude;
    /* type of data stored in index, or InvalidOid if variable */
    Oid         amkeytype;

    /* interface functions */
    ambuild_function ambuild;
    ambuildempty_function ambuildempty;
    aminsert_function aminsert;
    ambulkdelete_function ambulkdelete;
    amvacuumcleanup_function amvacuumcleanup;
    amcanreturn_function amcanreturn;   /* can be NULL */
    amcostestimate_function amcostestimate;
    amoptions_function amoptions;
    amproperty_function amproperty;     /* can be NULL */
    ambuildphasename_function ambuildphasename;   /* can be NULL */
    amvalidate_function amvalidate;
    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 */
} IndexAmRoutine;

要想真正有用,一个索引访问方法还必须有一个或多个定义在pg_opfamilypg_opclasspg_amoppg_amproc中的操作符族操作符类。这些项允许规划器判断哪种查询条件适用于这个索引访问方法的索引。操作符族和类在第 37.16 节中描述,它是阅读本章所需的前导材料。

一个独立的索引是由一个pg_class项定义的,该项描述索引为一个物理关系。还要加上一个pg_index项来显示索引的逻辑内容 — 也就是说,它所拥有的索引列集以及这些列的语义是被相关操作符类刻画的。索引列(键值)可以是底层表的 简单列,也可以是该表行上的表达式。索引访问方法通常不关心索引的键值来自那里(它总是操作预计算过的键值),但是它会对pg_index中的操作符类信息很感兴趣。所有这些目录项都可以被当作关系数据结构的一部分访问,这个数据结构会被传递给索引上的所有操作。

IndexAmRoutine中的有些标志域的含义并不那么直观。amcanunique的要求在第 61.5 节中讨论。amcanmulticol标志断言该索引访问方法支持多列索引, amoptionalkey断言它允许对那种在第一个索引列上没有给出可索引限制子句的扫描。如果amcanmulticol为假,那么amoptionalkey实际上说的是该访问方法是否允许不带限制子句的全索引扫描。 那些支持多索引列的访问方法必须支持那些在省略了除第一个列之外的任何或所有其它列上约束的扫描;不过,它们被允许去要求在第一个列上出现一些限制,并且这一点是以把amoptionalkey设置为假作为标志的。一个索引 AM 可能将amoptionalkey设置为假的一种原因是,如果它不索引空值。因为大多数可索引的操作符都是严格的并且因此不能对空输入返回真,所以不为空值存储索引项咋看上去很吸引人:因为它们不 可能被一个索引扫描返回。不过,当一个索引扫描对于一个给定索引列上没有约束子句时,这种讨论就不成立了。实际上,这意 味着设置了amoptionalkey为真的索引必须索引空值,因为规划器可能会决定在根本没有扫描键的时候使用这样的索引。一个相关的限制是一个支持 多索引列的索引访问方法必须支持索引第一列之后的列中的空值,因 为规划器会认为这个索引可以用于在那些列上没有限制的查询。例如,考虑一个在(a,b)上的索引和一个有WHERE a = 4的查询。系统会认为该索引可以用于扫描 a = 4的行, 如果索引忽略了 b 为空的行,那么就是错误的。不过,忽略那些在第一个索引列上值为空的行是 OK 的。一个索引空的索引访问方法可能也会设置amsearchnulls,表明它支持将IS NULLIS NOT NULL子句作为搜索条件。