每一个索引访问方法都由pg_am
系统目录中的一行所描述。pg_am
项为该索引访问方法指定了名称和一个处理器函数。这些项可以用CREATE ACCESS METHOD和DROP ACCESS METHOD SQL 命令创建和删除。
一个索引访问方法的处理器函数必须被声明为接受单一的类型为internal
类型的参数并且返回伪类型index_am_handler
。该参数是一个无用值,它只是被用来防止从 SQL 命令直接调用处理器函数。该函数的结果必须是一个已经 palloc 过的IndexAmRoutine
类型结构,它包含核心代码使用该索引访问方法所需的所有信息。IndexAmRoutine
结构(也被称为访问方法的API 结构)中的域指定了该访问方法的各种固定性质,例如它是否支持多列索引。更重要的是,它包含用于该访问方法的支持函数的指针,这些函数会完成真正访问索引的工作。这些支持函数是纯 C 函数,并且在 SQL 层面不可见也不可调用。支持函数在第 64.2 节中介绍。
结构体 IndexAmRoutine
定义如下:
typedef struct IndexAmRoutine { NodeTag type; /* * 通过该访问方法(AM)可以遍历/搜索的策略(操作符)总数。 * 如果AM没有固定的策略分配集,则为零。 */ uint16 amstrategies; /* 该访问方法使用的支持函数总数 */ uint16 amsupport; /* 操作类选项支持函数编号或0 */ uint16 amoptsprocnum; /* 访问方法是否支持按索引列的值进行排序? */ bool amcanorder; /* 访问方法是否支持按索引列上的操作符结果进行排序? */ bool amcanorderbyop; /* 访问方法是否支持向后扫描? */ bool amcanbackward; /* 访问方法是否支持唯一索引? */ bool amcanunique; /* 访问方法是否支持多列索引? */ bool amcanmulticol; /* 访问方法是否要求扫描必须对第一个索引列有约束? */ bool amoptionalkey; /* 访问方法是否处理 ScalarArrayOpExpr 条件? */ bool amsearcharray; /* 访问方法是否处理 IS NULL/IS NOT NULL 条件? */ bool amsearchnulls; /* 索引存储的数据类型是否可以与列的数据类型不同? */ bool amstorage; /* 这种类型的索引是否可以被聚簇? */ bool amclusterable; /* 访问方法是否处理谓词锁? */ bool ampredlocks; /* 访问方法是否支持并行扫描? */ bool amcanparallel; /* 访问方法是否支持通过 INCLUDE 子句包含的列? */ bool amcaninclude; /* 访问方法是否使用 maintenance_work_mem? */ bool amusemaintenanceworkmem; /* 访问方法是否对元组进行汇总,至少汇总块中的所有元组到一个摘要中 */ bool amsummarizing; /* 并行清理的标志位或操作 */ uint8 amparallelvacuumoptions; /* 存储在索引中的数据类型,或如果可变则为 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 */ ambuildphasename_function ambuildphasename; /* 可以为 NULL */ amvalidate_function amvalidate; amadjustmembers_function amadjustmembers; /* 可以为 NULL */ 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 */ /* 支持并行索引扫描的接口函数 */ amestimateparallelscan_function amestimateparallelscan; /* 可以为 NULL */ aminitparallelscan_function aminitparallelscan; /* 可以为 NULL */ amparallelrescan_function amparallelrescan; /* 可以为 NULL */ } IndexAmRoutine;
要想真正有用,一个索引访问方法还必须有一个或多个定义在pg_opfamily
、
pg_opclass
、
pg_amop
和
pg_amproc
中的操作符族和操作符类。这些项允许规划器判断哪种查询条件适用于这个索引访问方法的索引。操作符族和类在第 38.16 节中描述,它是阅读本章所需的前导材料。
一个独立的索引是由一个pg_class
项定义的,该项描述索引为一个物理关系。还要加上一个pg_index
项来显示索引的逻辑内容 — 也就是说,它所拥有的索引列集以及这些列的语义是被相关操作符类刻画的。索引列(键值)可以是底层表的 简单列,也可以是该表行上的表达式。索引访问方法通常不关心索引的键值来自那里(它总是操作预计算过的键值),但是它会对pg_index
中的操作符类信息很感兴趣。所有这些目录项都可以被当作关系
数据结构的一部分访问,这个数据结构会被传递给索引上的所有操作。
IndexAmRoutine
中的有些标志域的含义并不那么直观。amcanunique
的要求在第 64.5 节中讨论。amcanmulticol
标志断言该索引访问方法支持多键列索引, amoptionalkey
断言它允许对那种在第一个索引列上没有给出可索引限制子句的扫描。如果amcanmulticol
为假,那么amoptionalkey
实际上说的是该访问方法是否允许不带限制子句的全索引扫描。 那些支持多索引列的访问方法必须支持那些在省略了除第一个列之外的任何或所有其它列上约束的扫描;不过,它们被允许去要求在第一个列上出现一些限制,并且这一点是以把amoptionalkey
设置为假作为标志的。一个索引 AM 可能将amoptionalkey
设置为假的一种原因是,如果它不索引空值。因为大多数可索引的操作符都是严格的并且因此不能对空输入返回真,所以不为空值存储索引项咋看上去很吸引人:因为它们不 可能被一个索引扫描返回。不过,当一个索引扫描对于一个给定索引列上没有约束子句时,这种讨论就不成立了。实际上,这意 味着设置了amoptionalkey
为真的索引必须索引空值,因为规划器可能会决定在根本没有扫描键的时候使用这样的索引。一个相关的限制是一个支持 多索引列的索引访问方法必须支持索引第一列之后的列中的空值,因 为规划器会认为这个索引可以用于在那些列上没有限制的查询。例如,考虑一个在(a,b)上的索引和一个有WHERE a = 4
的查询。系统会认为该索引可以用于扫描 a = 4
的行, 如果索引忽略了 b 为空的行,那么就是错误的。不过,忽略那些在第一个索引列上值为空的行是 OK 的。一个索引空的索引访问方法可能也会设置amsearchnulls
,表明它支持将IS NULL
和IS NOT NULL
子句作为搜索条件。
amcaninclude
一个标志指示此访问方法是否支持 “included”列,这些列可以包含除键列之外的其他列(不处理它们) 。上一段中的要求仅适用于关键列。除其他外,amcanmulticol
=false
和 amcaninclude
=true
组合是实用的。这表明可以有包含列,而只有一个键列。此外,amoptionalkey
独立地,包含列必须可以为空。
amsummarizing
标志指示访问方法是否对索引元组进行汇总,
汇总的粒度至少为每块。
不指向单个元组,而是指向块范围的访问方法(如BRIN),
可能允许HOT优化继续。这不适用于索引谓词中引用的属性,
更新此类属性总是会禁用HOT。