pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
本章定义 PostgreSQL 核心系统与管理各个索引类型的索引访问方法之间的接口。除这里规定的内容之外,核心系统对索引一无所知,因此可以通过编写附加代码来开发全新的索引类型。
PostgreSQL 中所有索引在技术上都称为二级索引;也就是说,索引与它所描述的表文件在物理上是分离的。每个索引都存储为独立的物理关系,因此在 pg_class 目录中都有一个条目。索引的内容完全由其索引访问方法控制。实践中,所有索引访问方法都会把索引划分为标准大小的页面,以便使用常规的存储管理器和缓冲区管理器访问索引内容。(现有的所有索引访问方法还都使用 第 65.6 节 中描述的标准页面布局,而且大多数还对索引元组头使用相同的格式;但这些决定并不是访问方法必须遵循的。)
索引本质上是从一些数据键值到行版本(元组)在索引父表中的元组标识符(TIDs)的映射。一个 TID 由块号以及该块中的项号组成(见 第 65.6 节)。这些信息足以从表中取出某个特定的行版本。索引并不直接知道在 MVCC 之下同一逻辑行可能会存在多个现存版本;对索引来说,每个元组都是一个独立对象,需要它自己的索引条目。因此,对一行的更新总会为该行创建全新的索引条目,即使键值并未改变也是如此。(HOT 元组是这一说法的例外;但索引同样不直接处理它们。)当死元组自身被回收时(通过清理),它们对应的索引条目也会被回收。
每种索引访问方法都由系统目录 pg_am 中的一行描述。pg_am 条目指定了该访问方法的名称以及一个处理器函数。这些条目可以通过 CREATE ACCESS METHOD 和 DROP ACCESS METHOD SQL 命令创建和删除。
索引访问方法的处理器函数必须声明为接受一个 internal 类型的参数,并返回伪类型 index_am_handler。这个参数只是一个占位值,用来防止处理器函数被 SQL 命令直接调用。该函数的结果必须是一个通过 palloc 分配的 IndexAmRoutine 结构体,其中包含核心代码使用该索引访问方法所需的全部信息。IndexAmRoutine 结构体也称为该访问方法的API 结构体,其中的字段指定了访问方法的各种固定属性,例如它是否支持多列索引。更重要的是,它包含该访问方法的支持函数指针,而这些支持函数完成了实际访问索引的全部工作。这些支持函数是普通 C 函数,在 SQL 层不可见也不可调用。支持函数将在 第 59.2 节 中介绍。
结构体IndexAmRoutine定义如下:
typedef struct IndexAmRoutine
{
NodeTag type;
/*
* 可用于遍历或搜索此 AM 的策略(操作符)总数。
* 如果 AM 没有固定的一组策略分配,则为零。
*/
uint16 amstrategies;
/* 此 AM 使用的支持函数总数 */
uint16 amsupport;
/* AM 是否支持按被索引列的值进行 ORDER BY? */
bool amcanorder;
/* AM 是否支持按被索引列上操作符的结果进行 ORDER BY? */
bool amcanorderbyop;
/* AM 是否支持反向扫描? */
bool amcanbackward;
/* AM 是否支持 UNIQUE 索引? */
bool amcanunique;
/* AM 是否支持多列索引? */
bool amcanmulticol;
/* AM 是否要求扫描必须约束第一个索引列? */
bool amoptionalkey;
/* AM 是否处理 ScalarArrayOpExpr 限定条件? */
bool amsearcharray;
/* AM 是否处理 IS NULL/IS NOT NULL 限定条件? */
bool amsearchnulls;
/* 索引存储数据类型是否可以不同于列数据类型? */
bool amstorage;
/* 是否可以按此类型的索引进行聚簇? */
bool amclusterable;
/* AM 是否处理谓词锁? */
bool ampredlocks;
/* 索引中存储的数据类型;如果可变,则为 InvalidOid */
Oid amkeytype;
/* 接口函数 */
ambuild_function ambuild;
ambuildempty_function ambuildempty;
aminsert_function aminsert;
ambulkdelete_function ambulkdelete;
amvacuumcleanup_function amvacuumcleanup;
amcanreturn_function amcanreturn; /* 可以为 NULL */
amcostestimate_function amcostestimate;
amoptions_function amoptions;
amproperty_function amproperty; /* 可以为 NULL */
amvalidate_function amvalidate;
ambeginscan_function ambeginscan;
amrescan_function amrescan;
amgettuple_function amgettuple; /* 可以为 NULL */
amgetbitmap_function amgetbitmap; /* 可以为 NULL */
amendscan_function amendscan;
ammarkpos_function ammarkpos; /* 可以为 NULL */
amrestrpos_function amrestrpos; /* 可以为 NULL */
} IndexAmRoutine;
要使一种索引访问方法真正可用,还必须在 pg_opfamily、pg_opclass、pg_amop 和 pg_amproc 中定义一个或多个操作符族和操作符类。这些条目使规划器能够确定该访问方法的索引可以使用哪些查询限定条件。关于操作符族和操作符类的说明见 第 36.14 节;阅读本章前应先掌握这些内容。
单个索引由两个系统目录条目共同定义:一个 pg_class 条目把它描述为一个物理关系,另一个 pg_index 条目给出索引的逻辑内容,也就是它包含哪些索引列,以及由相关操作符类定义的这些列的语义。索引列(键值)既可以是底层表的简单列,也可以是基于表行的表达式。索引访问方法通常并不关心索引键值来自何处(它拿到的总是预先计算好的键值),但会非常关心 pg_index 中的操作符类信息。这两个目录条目都可以作为传递给索引上所有操作的 Relation 数据结构的一部分来访问。
IndexAmRoutine 的某些标志字段还有一些不那么直观的含义。amcanunique 的要求见 第 59.5 节。amcanmulticol 标志表明该访问方法支持多列索引,而 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 NULL 和 IS NOT NULL 子句作为搜索条件。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。