选择 打开 改范围 完整检索页
受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10
当前 PostgreSQL 版本不在支持生命周期内。
您可以参阅当前版本的对应页面,或其他在上面列出的活跃大版本。

61.2. 索引访问方法函数 #

索引访问方法必须在 IndexAmRoutine 中提供如下索引构建和维护函数:

IndexBuildResult *
ambuild (Relation heapRelation,
         Relation indexRelation,
         IndexInfo *indexInfo);

构建新索引。索引关系已在物理上创建,但内容为空。必须填入访问方法所需的固定数据,以及表中所有已有元组对应的条目。通常,ambuild函数会调用IndexBuildHeapScan()扫描表中现有的元组,并计算需要插入索引的键。函数必须返回一个通过 palloc 分配的结构体,其中包含新索引的统计信息。

void
ambuildempty (Relation indexRelation);

构建一个空索引,并将其写入给定关系的初始化分支(INIT_FORKNUM)。只有不记录 WAL 的索引才会调用此方法;写入初始化分支的空索引会在每次服务器重启时复制到主关系分支上。

bool
aminsert (Relation indexRelation,
          Datum *values,
          bool *isnull,
          ItemPointer heap_tid,
          Relation heapRelation,
          IndexUniqueCheck checkUnique,
          IndexInfo *indexInfo);

将一个新元组插入已有索引。valuesisnull数组给出要索引的键值,heap_tid则是要索引的 TID。如果访问方法支持唯一索引(其amcanunique标志为 true),则checkUnique指明要执行的唯一性检查类型,具体取决于唯一约束是否可延迟;详情参见Section 61.5。通常,访问方法只在执行唯一性检查时才需要heapRelation参数(因为此时必须查看堆以确认元组是否存活)。

该函数返回的布尔值只有在 checkUniqueUNIQUE_CHECK_PARTIAL 时才有意义。在这种情况下,返回真表示新条目已知是唯一的,而返回假表示它可能不是唯一的(因此必须安排一次延迟的唯一性重检)。对于其他情况,建议恒定返回假。

某些索引可能不会为所有元组建立索引。如果某个元组不应被索引,aminsert 应当直接返回而不做任何事。

如果索引 AM 希望在一条 SQL 语句中的连续索引插入之间缓存数据,它可以在 indexInfo->ii_Context 中分配空间,并把数据指针存入 indexInfo->ii_AmCache(该指针初始为 NULL)。

IndexBulkDeleteResult *
ambulkdelete (IndexVacuumInfo *info,
              IndexBulkDeleteResult *stats,
              IndexBulkDeleteCallback callback,
              void *callback_state);

从索引中删除元组。这是一个批量删除操作,通常应通过扫描整个索引并检查每个条目是否应被删除来实现。必须调用传入的 callback 函数,其调用形式为 callback(TID, callback_state) returns bool,以确定由其引用 TID 标识的某个索引条目是否应删除。该函数必须返回 NULL,或者返回一个通过 palloc 分配的结构体,其中包含此次删除操作影响的统计信息。如果不需要向 amvacuumcleanup 传递信息,返回 NULL 也是可以的。

由于 maintenance_work_mem 有限,当待删除的元组很多时,ambulkdelete 可能需要被调用多次。参数 stats 是此前对该索引上一次调用的结果(在一次 VACUUM 操作中的第一次调用时它为 NULL)。这使得 AM 可以在整个操作过程中累积统计信息。通常,如果传入的 stats 非 NULL,ambulkdelete 会修改并返回同一个结构体。

IndexBulkDeleteResult *
amvacuumcleanup (IndexVacuumInfo *info,
                 IndexBulkDeleteResult *stats);

在一次 VACUUM 操作(零次或多次 ambulkdelete 调用)之后执行清理。它不一定要做返回索引统计信息之外的事情,但也可能执行批量清理,例如回收空索引页面。stats 是最后一次 ambulkdelete 调用返回的结果;如果因为没有需要删除的元组而没有调用 ambulkdelete,则为 NULL。如果结果不是 NULL,它必须是一个通过 palloc 分配的结构体。其中的统计信息将用于更新 pg_class,并在指定了 VERBOSE 时由 VACUUM 报告。如果索引在整个 VACUUM 操作期间完全没有变化,返回 NULL 也是可以的;否则应返回正确的统计信息。

PostgreSQL 8.4 开始,amvacuumcleanup 也会在 ANALYZE 操作完成时被调用。在这种情况下,stats 总是 NULL,而且任何返回值都会被忽略。这种情况可以通过检查 info->analyze_only 来区分。我们建议访问方法在这种调用中除了做插入后的清理外什么都不做,而且只在自动清理工作进程中这样做。

bool
amcanreturn (Relation indexRelation, int attno);

检查索引是否可以支持仅索引扫描,方法是返回给定列的原始被索引值。属性编号从 1 开始,即第一列的 attno 为 1。支持时返回真,否则返回假。对于包含列(如果支持),此函数应始终返回真,因为无法取回值的包含列没有什么意义。如果访问方法完全不支持仅索引扫描,则其amcanreturn字段在其IndexAmRoutine结构体中可以设为 NULL。

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation,
                double *indexPages);

估计一次索引扫描的开销。该函数的完整说明见Section 61.6(见下文)。

bytea *
amoptions (ArrayType *reloptions,
           bool validate);

解析并验证索引的 reloptions 数组。只有当该索引存在非空的 reloptions 数组时才会调用此函数。reloptions 是一个 text 数组,其中的条目形如 name=value。该函数应构造一个 bytea 值,并将其复制到索引 relcache 条目的 rd_options 字段中。这个 bytea 值中的数据内容由访问方法自行定义;大多数标准访问方法使用结构体 StdRdOptions。当 validate 为真时,若存在未识别选项或无效取值,函数应报告合适的错误消息;当 validate 为假时,无效条目应被静默忽略。(当装载已存储在 pg_catalog 中的选项时,validate 为假;此时只有在访问方法改变了选项规则时才可能发现无效条目,而忽略过时条目是合适的。)如果希望采用默认行为,返回 NULL 也是可以的。

bool
amproperty (Oid index_oid, int attno,
            IndexAMProperty prop, const char *propname,
            bool *res, bool *isnull);

amproperty方法允许索引访问方法覆盖pg_index_column_has_property及其相关函数的默认行为。如果访问方法对索引属性查询没有任何特殊行为,那么amproperty字段在其IndexAmRoutine结构体中可以设为 NULL。否则,amproperty方法会收到如下调用参数:index_oidattno均为零,对应pg_indexam_has_property调用;或者index_oid有效且attno为零,对应pg_index_has_property调用;或者index_oid有效且attno大于零,对应pg_index_column_has_property调用。prop是一个枚举值,用来标识当前测试的属性;propname则是原始属性名字符串。如果核心代码不认识该属性名,那么propAMPROP_UNKNOWN。访问方法可以通过检查propname是否匹配来定义自定义属性名(使用pg_strcasecmp进行匹配,以与核心代码保持一致);对于核心代码已知的名称,最好检查prop。如果amproperty方法返回true,则表示它已确定属性测试结果:它必须将*res设为要返回的布尔值,或者把*isnull设为true以返回 NULL。(所引用的两个变量在调用前都初始化为false。)如果amproperty方法返回false,核心代码就会按其正常逻辑确定属性测试结果。

支持排序操作符的访问方法应当实现 AMPROP_DISTANCE_ORDERABLE 属性测试,因为核心代码不知道如何完成该测试,只会返回 NULL。若实现成本低于打开索引并调用 amcanreturn(这正是核心代码的默认行为),那么实现 AMPROP_RETURNABLE 测试也可能是有利的。对于其他所有标准属性,默认行为应当已经足够。

bool
amvalidate (Oid opclassoid);

在访问方法能够合理验证的范围内,验证指定操作符类的目录条目。例如,可以检查是否提供了所有必需的支持函数。如果操作符类无效,amvalidate函数必须返回 false。应通过ereport消息报告。

索引的目的当然是支持扫描那些匹配可索引 WHERE 条件的元组,这种条件常被称为限定词扫描键。关于索引扫描的语义,将在下面的 Section 61.3 中更详细地说明。一种索引访问方法可以支持普通索引扫描、位图索引扫描,或者两者都支持。索引访问方法必须或可以提供的扫描相关函数如下:

IndexScanDesc
ambeginscan (Relation indexRelation,
             int nkeys,
             int norderbys);

为索引扫描做准备。nkeysnorderbys参数指明扫描中将使用的限定条件和排序操作符的数量,可用于分配空间。注意,此时尚未提供扫描键的实际值。结果必须是用 palloc 分配的结构体。由于实现方面的原因,索引访问方法必须调用RelationGetIndexScan()来创建此结构体。大多数情况下,ambeginscan除了执行该调用以及可能获取锁之外,几乎不做其他工作;索引扫描启动过程的主要工作位于amrescan

void
amrescan (IndexScanDesc scan,
          ScanKey keys,
          int nkeys,
          ScanKey orderbys,
          int norderbys);

开始或重新开始一次索引扫描,并且可以使用新的扫描键。(若要用之前传入的键重新开始,则为 keys 和/或 orderbys 传递 NULL。)请注意,键的数量或排序操作符的数量都不允许超过传给 ambeginscan 的数量。实践中,这种重启特性通常用于嵌套循环连接选中了新的外层元组,因此需要新的键比较值,但扫描键结构体本身保持不变的场景。

bool
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

沿给定方向(在索引中向前或向后)获取给定扫描中的下一个元组。如果取得元组,则返回 true;如果没有剩余的匹配元组,则返回 false。返回 true 时,将元组 TID 存储到scan结构体中。注意,成功仅表示索引中包含与扫描键匹配的条目,并不表示该元组一定仍存在于堆中,或者能够通过调用方的快照测试。成功时,amgettuple还必须将scan->xs_recheck设为 true 或 false。false 表示可以确定索引条目与扫描键匹配;true 表示尚不确定,在获取堆元组后,必须对它重新检查扫描键所表示的条件。这一规定支持有损索引操作符。注意,重新检查仅针对扫描条件;部分索引谓词(如果有)永远不会由amgettuple的调用方重新检查。

如果索引支持 仅索引扫描(即其任一列上 amcanreturn 返回真),那么成功时 AM 还必须检查 scan->xs_want_itup;若其为真,就必须返回该索引条目的原始被索引数据。对于 amcanreturn 返回假的列,可以把其值作为 NULL 返回。数据可以通过以下两种形式之一返回:一种是把 IndexTuple 指针存入 scan->xs_itup,其元组描述符为 scan->xs_itupdesc;另一种是把 HeapTuple 指针存入 scan->xs_hitup,其元组描述符为 scan->xs_hitupdesc。(当重建出的数据可能装不进 IndexTuple 时,应使用后一种格式。)无论采用哪种形式,指针所引用数据的管理责任都在访问方法一侧。对这份数据的有效性必须至少保持到该扫描下一次调用 amgettupleamrescanamendscan 为止。

只有当访问方法支持普通索引扫描时,才需要提供 amgettuple 函数。如果不支持,其 IndexAmRoutine 结构体中的 amgettuple 字段必须设为 NULL。

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

获取给定扫描中的所有元组,并将其加入调用方提供的TIDBitmap中(即将元组 ID 集合与位图中已有的集合做 OR 运算)。返回获取到的元组数量(可能只是近似值,例如某些访问方法不检测重复项)。向位图插入元组 ID 时,amgetbitmap可以指明需要对特定元组 ID 重新检查扫描条件。这类似于xs_recheck输出参数,它属于amgettuple。注意:在当前实现中,此特性的支持与位图自身有损存储的支持混在一起,因此对于需要重查的元组,调用方会重新检查扫描条件和部分索引谓词(如果有)。但将来未必始终如此。amgetbitmapamgettuple不能在同一次索引扫描中使用;使用amgetbitmap时还有其他限制,详见Section 61.3

只有当访问方法支持位图索引扫描时,才需要提供 amgetbitmap 函数。如果不支持,其 IndexAmRoutine 结构体中的 amgetbitmap 字段必须设为 NULL。

void
amendscan (IndexScanDesc scan);

结束一次扫描并释放资源。scan 结构体本身不应被释放,但访问方法内部获取的任何锁或 pin,以及由 ambeginscan 和其他扫描相关函数分配的其他内存,都必须被释放。

void
ammarkpos (IndexScanDesc scan);

标记当前扫描位置。访问方法只需要为每次扫描支持一个被记住的扫描位置。

只有当访问方法支持有序扫描时,才需要提供 ammarkpos 函数。如果不支持,其 IndexAmRoutine 结构体中的 ammarkpos 字段可以设为 NULL。

void
amrestrpos (IndexScanDesc scan);

将扫描恢复到最近一次标记的位置。

只有当访问方法支持有序扫描时,才需要提供 amrestrpos 函数。如果不支持,其 IndexAmRoutine 结构体中的 amrestrpos 字段可以设为 NULL。

除了支持普通索引扫描外,某些类型的索引还可能希望支持并行索引扫描,这允许多个后端协同执行一次索引扫描。索引访问方法应安排好实现方式,使每个参与协作的进程返回普通非并行索引扫描本应返回的元组集合的一个子集,而且这些子集的并集应等于普通非并行索引扫描的结果集。此外,虽然并行扫描返回的元组不必具有全局顺序,但每个协作后端内部返回的那个子集的顺序必须匹配请求的顺序。可以实现下列函数来支持并行索引扫描:

Size
amestimateparallelscan (void);

估算并返回访问方法执行并行扫描所需的动态共享内存字节数。(此数值是额外需要的空间,不包括以下结构体中与访问方法无关的数据所需的空间:ParallelIndexScanDescData。)

对于不支持并行扫描,或者所需额外存储字节数为零的访问方法,无需实现此函数。

void
aminitparallelscan (void *target);

在并行扫描开始时,会调用这个函数来初始化动态共享内存。target 会指向一块动态共享内存,其大小至少等于此前 amestimateparallelscan 返回的字节数,而该函数可以用这部分空间存储它想保存的任何数据。

对于不支持并行扫描,或者虽需要共享内存但无需初始化的情况,无需实现此函数。

void
amparallelrescan (IndexScanDesc scan);

如果实现了此函数,当并行索引扫描必须重新开始时就会调用它。它应重置由 aminitparallelscan 建立的任何共享状态,以便扫描从头开始重新执行。