↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1
历史版本。 PostgreSQL 13 已结束支持。 2025-11-13. 请参阅 当前版本手册.

61.2. 索引访问方法函数 #

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

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

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

void
ambuildempty (Relation indexRelation);

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

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

向现有索引中插入一个新元组。values 和 isnull 数组给出要建立索引的键值,而 heap_tid 是要建立索引的 TID。如果该访问方法支持唯一索引(其 amcanunique 标志为真),那么 checkUnique 指示要执行的唯一性检查类型。这取决于唯一约束是否可延迟;详见第 61.5 节。通常,访问方法只会在执行唯一性检查时需要 heapRelation 参数(因为这时它必须到堆中核实元组是否仍然存活)。

该函数返回的布尔值只有在 checkUnique 为 UNIQUE_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。若支持则返回真,否则返回假。如果支持内含列,这个函数应始终对内含列返回真,因为不能取回的内含列几乎没有意义。如果访问方法根本不支持仅索引扫描,那么它的 IndexAmRoutine 结构体中的 amcanreturn 字段可以设为 NULL。

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

估计一次索引扫描的代价。该函数将在后面的第 61.6 节中详细讨论。

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

解析并验证索引的 reloptions 数组。只有当该索引存在非 NULL 的 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_oid 和 attno 均为零,对应 pg_indexam_has_property 调用;或者 index_oid 有效且 attno 为零,对应 pg_index_has_property 调用;或者 index_oid 有效且 attno 大于零,对应 pg_index_column_has_property 调用。prop 是一个枚举值,用来标识当前测试的属性;propname 则是原始属性名字符串。如果核心代码不认识该属性名,那么 prop 为 AMPROP_UNKNOWN。访问方法可以通过检查 propname 是否匹配来定义自定义属性名(使用 pg_strcasecmp 进行匹配,以与核心代码保持一致);对于核心代码已知的名称,最好检查 prop。如果 amproperty 方法返回 true,则表示它已确定属性测试结果:它必须将*res 设为要返回的布尔值,或者把*isnull 设为 true 以返回 NULL。(所引用的两个变量在调用前都初始化为 false。)如果 amproperty 方法返回 false,核心代码就会按其正常逻辑确定属性测试结果。

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

char *
ambuildphasename (int64 phasenum);

返回给定构建阶段编号的文本名称。阶段编号是在索引构建期间通过 pgstat_progress_update_param 接口报告的,随后阶段名称会出现在 pg_stat_progress_create_index 视图中。

bool
amvalidate (Oid opclassoid);

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

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

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

为一次索引扫描做准备。nkeys 和 norderbys 参数表示扫描中将使用的限定条件和排序操作符数量,这些信息可能有助于空间分配。请注意,此时还没有提供扫描键的实际值。结果必须是一个通过 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);

在给定扫描中取出下一个元组,并按给定方向移动(在索引中向前或向后)。若成功取得元组则返回真;若不再有匹配元组则返回假。在返回真时,元组的 TID 会存入 scan 结构体中。请注意,“成功”仅表示索引中存在一个匹配扫描键的条目,并不表示该元组一定仍然存在于堆中,或者一定能通过调用者的快照测试。成功时,amgettuple 还必须把 scan->xs_recheck 设为真或假。假表示可以确定该索引条目匹配扫描键;真表示这一点并不确定,因此在取到堆元组后必须根据扫描键所代表的条件重新检查它。这一机制支持“有损”索引操作符。请注意,重检只针对扫描条件;部分索引谓词(如果有)永远不会由 amgettuple 的调用者重新检查。

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

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

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

取出给定扫描中的所有元组,并将其加入调用者提供的 TIDBitmap 中(也就是把这组元组 ID 与位图中已有的集合做 OR)。返回值是取得的元组数量(这可能只是近似计数,例如某些 AM 不会检测重复项)。在把元组 ID 插入位图时,amgetbitmap 可以指出某些具体的元组 ID 需要重新检查扫描条件。这类似于 amgettuple 的 xs_recheck 输出参数。注意:在当前实现中,对这一特性的支持与位图自身的有损存储支持混在一起,因此调用者会对需要重检的元组同时重新检查扫描条件和部分索引谓词(如果有)。不过,这并不一定永远如此。amgetbitmap 和 amgettuple 不能在同一次索引扫描中同时使用;使用 amgetbitmap 时还有其他限制,详见第 61.3 节。

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

void
amendscan (IndexScanDesc scan);

结束一次扫描并释放资源。scan 结构体本身不应被释放,但必须释放访问方法内部获取的所有锁、解除所有钉住状态,并释放由 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 建立的任何共享状态,以便扫描从头开始重新执行。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.