2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 17 / 16 / 15 / 14 / 13
开发版本: devel
不支持的版本: 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

63.2. 索引访问方法函数 #

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

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

构建一个新索引。索引关系已物理创建,但为空。它必须用访问方法所需的任何固定数据以及表中所有现有元组的条目进行填充。通常,ambuild 函数将调用 table_index_build_scan() 来扫描表中的现有元组并计算需要插入索引的键。该函数必须返回一个包含新索引统计信息的 palloc'd 结构。 amcanbuildparallel 标志指示访问方法是否支持并行索引构建。如果设置为 true,系统将尝试为构建分配并行工作进程。仅支持非并行索引构建的访问方法应将此标志设置为 false

void
ambuildempty (Relation indexRelation);

构建一个空索引,并将其写入给定关系的初始化分叉 (INIT_FORKNUM)。此方法仅用于未记录的索引;写入初始化分叉的空索引将在每次服务器重新启动时复制到主关系分叉。

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

将新元组插入现有索引。 valuesisnull 数组给出了要索引的键值,heap_tid 是要索引的 TID。如果访问方法支持唯一索引(其 amcanunique 标志为 true),则 checkUnique 指示要执行的唯一性检查类型。这取决于唯一约束是否可延迟;有关详细信息,请参见 第 63.5 节。通常,访问方法在执行唯一性检查时仅需要 heapRelation 参数(因为那时它必须查看堆以验证元组的有效性)。

indexUnchanged 布尔值提供了一个关于要索引的元组性质的提示。当它为 true 时,元组是索引中现有元组的副本。新元组是逻辑上未更改的后续 MVCC 元组版本。当发生 UPDATE 且不修改任何由索引覆盖的列,但仍需要在索引中创建新版本时,会发生这种情况。索引 AM 可以使用此提示来决定在同一逻辑行的多个版本累积的索引部分应用自底向上的索引删除。请注意,更新非键列或仅出现在部分索引谓词中的列不会影响 indexUnchanged 的值。核心代码使用低开销的方法确定每个元组的 indexUnchanged 值,该方法允许假阳性和假阴性。索引 AM 不得将 indexUnchanged 视为关于元组可见性或版本化的权威信息来源。

仅当 checkUniqueUNIQUE_CHECK_PARTIAL 时,该函数的布尔结果值才具有意义。在这种情况下,true 结果表示新条目已知是唯一的,而 false 表示它可能是非唯一的(并且必须安排一个延迟的唯一性检查)。对于其他情况,建议使用恒定的 false 结果。

某些索引可能不会索引所有元组。如果元组不需要被索引,aminsert 应直接返回而不执行任何操作。

如果索引 AM 希望在 SQL 语句的连续索引插入之间缓存数据,它可以将空间分配给 indexInfo->ii_Context 并将指向数据的指针存储在 indexInfo->ii_AmCache 中(最初为 NULL)。如果需要在索引插入后释放内存以外的资源,可以提供 aminsertcleanup,它将在释放内存之前被调用。

void
aminsertcleanup (Relation indexRelation,
                 IndexInfo *indexInfo);

清理在 indexInfo->ii_AmCache 中跨连续插入维护的状态。如果数据需要额外的清理步骤(例如,释放固定的缓冲区),并且仅释放内存不足以满足要求,那么这很有用。

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

从索引中删除元组。这是一个“批量删除”操作,旨在通过扫描整个索引并检查每个条目是否应被删除来实现。必须按照 callback(TID, callback_state) returns bool 的样式调用传入的 callback 函数,以确定由其引用的 TID 标识的任何特定索引条目是否应被删除。必须返回 NULL 或一个包含有关删除操作效果的统计信息的 palloc'd 结构。如果没有需要传递给 amvacuumcleanup 的信息,返回 NULL 是可以接受的。

由于 maintenance_work_mem 的限制,当需要删除大量元组时,ambulkdelete 可能需要被调用多次。 stats 参数是此索引前一次调用的结果(对于 VACUUM 操作中的第一次调用,它是 NULL)。这允许 AM 跨整个操作累积统计信息。通常,如果传入的 stats 非空,ambulkdelete 将修改并返回相同的结构。

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

VACUUM 操作(零次或多次 ambulkdelete 调用)完成后进行清理。除了返回索引统计信息外,这不必做任何事情,但它可能会执行批量清理,例如回收空的索引页。stats 是最后一个 ambulkdelete 调用返回的任何内容,或者如果 ambulkdelete 未被调用(因为不需要删除任何元组),则为 NULL。如果结果非 NULL,则必须是 palloc'd 结构。其中包含的统计信息将用于更新 pg_class,并在给出 VERBOSE 时由 VACUUM 报告。如果索引在 VACUUM 操作期间没有发生任何变化,返回 NULL 是可以接受的,但否则应返回正确的统计信息。

amvacuumcleanup 也将在 ANALYZE 操作完成时被调用。在这种情况下,stats 始终为 NULL,并且任何返回值都将被忽略。可以通过检查 info->analyze_only 来区分这种情况。建议访问方法除了在 autovacuum 工作进程中进行插入后清理外,不做任何其他事情。

bool
amcanreturn (Relation indexRelation, int attno);

通过返回列的原始索引值来检查索引是否支持给定列上的 仅索引扫描。属性号是 1 基的,即第一个列的 attno 是 1。如果支持,则返回 true,否则返回 false。此函数对于包含的列(如果支持)应始终返回 true,因为一个无法检索的包含列意义不大。如果访问方法根本不支持仅索引扫描,可以在其 IndexAmRoutine 结构中将 amcanreturn 字段设置为 NULL。

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

估算索引扫描的成本。此函数将在下面的 第 63.6 节 中得到全面描述。

int
amgettreeheight (Relation rel);

计算树形索引的高度。此信息将作为 path->indexinfo->tree_height 提供给 amcostestimate 函数,并可用于支持成本估算。结果未在其他任何地方使用,因此此函数实际上可用于计算成本估算函数可能想知道(适合整数)的任何类型的索引数据。如果计算成本很高,将其结果作为 RelationData.rd_amcache 的一部分进行缓存可能会很有用。

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

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

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

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

支持排序运算符的访问方法应实现 AMPROP_DISTANCE_ORDERABLE 属性测试,因为核心代码不知道如何进行此操作,并将返回 NULL。如果 AMPROP_RETURNABLE 测试比打开索引并调用 amcanreturn(这是核心代码的默认行为)更便宜,那么实现此测试也可能是有益的。对于所有其他标准属性,默认行为应该令人满意。

char *
ambuildphasename (int64 phasenum);

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

bool
amvalidate (Oid opclassoid);

在合理范围内验证指定运算符类的目录条目。例如,这可能包括测试是否提供了所有必需的支持函数。如果 opclass 无效,amvalidate 函数必须返回 false。应使用 ereport 消息报告问题,通常在 INFO 级别。

void
amadjustmembers (Oid opfamilyoid,
                 Oid opclassoid,
                 List *operators,
                 List *functions);

在合理范围内验证运算符族的拟议新运算符和函数成员,并在默认依赖类型不满意时设置它们的依赖类型。这会在 CREATE OPERATOR CLASSALTER OPERATOR FAMILY ADD 期间调用(后一种情况下的 opclassoidInvalidOid)。 List 参数是 amapi.h 中定义的 OpFamilyMember 结构列表。此函数进行的测试通常是 amvalidate 执行的测试的子集,因为 amadjustmembers 不能假定它看到的是完整的成员集。例如,检查支持函数的签名是合理的,但检查是否提供了所有必需的支持函数则不然。任何问题都可以通过抛出错误来报告。 OpFamilyMember 结构中的依赖项相关字段由核心代码初始化,以在 CREATE OPERATOR CLASS 的情况下创建对 opclass 的硬依赖,或在 ALTER OPERATOR FAMILY ADD 的情况下创建对 opfamily 的软依赖。amadjustmembers 可以调整这些字段,如果其他行为更合适。例如,GIN、GiST 和 SP-GiST 总是将运算符成员设置为对 opfamily 具有软依赖,因为在这些索引类型中,运算符与 opclass 之间的连接相对较弱;因此,允许自由添加和删除运算符成员是合理的。可选的支持函数通常也具有软依赖,以便在必要时可以删除它们。

索引的目的是支持扫描匹配可索引的 WHERE 条件的元组,这通常称为 限定符扫描键。索引扫描的语义将在下面的 第 63.3 节 中得到更全面的描述。索引访问方法可以支持“普通”索引扫描、“位图”索引扫描,或两者都支持。索引访问方法必须或可能提供的与扫描相关的函数是:

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

为索引扫描做准备。nkeysnorderbys 参数指示将在扫描中使用的限定符和排序运算符的数量;这些可能对空间分配有用。请注意,扫描键的实际值尚未提供。结果必须是一个 palloc'd 结构。出于实现原因,索引访问方法 必须 通过调用 RelationGetIndexScan() 来创建此结构。在大多数情况下,ambeginscan 除了进行该调用和可能获取锁之外,几乎不做其他事情;索引扫描启动的有趣部分在 amrescan 中。

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

启动或重新启动索引扫描,可能带有新的扫描键。(要使用先前传递的键重新启动,请将 NULL 传递给 keys 和/或 orderbys。)请注意,键的数量或排序运算符的数量不能大于传递给 ambeginscan 的值。实际上,当嵌套循环连接选择了一个新的外部元组,因此需要一个新的键比较值时,就会使用重新启动功能,但扫描键结构保持不变。

bool
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

获取给定扫描中的下一个元组,按照指定的方向(在索引中向前或向后)移动。如果获得了元组,则返回 true,如果不再有匹配的元组,则返回 false。在 true 的情况下,元组 TID 将存储在 scan 结构中。请注意,“成功”仅表示索引包含与扫描键匹配的条目,而不表示元组必然仍然存在于堆中或将通过调用者的快照测试。成功时,amgettuple 还必须将 scan->xs_recheck 设置为 true 或 false。False 表示可以确定索引条目与扫描键匹配。True 表示不确定,并且必须在获取元组后重新检查堆元组的扫描键所表示的条件。此规定支持“有损”索引运算符。请注意,重新检查仅限于扫描条件;对于部分索引谓词(如果存在),amgettuple 调用者永远不会重新检查。

如果索引支持 仅索引扫描(即,amcanreturn 为其任何列返回 true),那么成功时 AM 还必须检查 scan->xs_want_itup,如果为 true,则必须返回索引条目的原始索引数据。对于 amcanreturn 返回 false 的列可以返回为 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 操作)。返回获取的元组数量(这可能只是一个近似计数,例如一些 AM 不检测重复项)。在将元组 ID 插入位图时,amgetbitmap 可以指示需要重新检查特定元组 ID 的扫描条件。这类似于 amgettuplexs_recheck 输出参数。注意:在当前实现中,对该功能的支持与位图本身的无损存储支持混淆,因此调用者会重新检查扫描条件和部分索引谓词(如果存在)以获取可重检查的元组。但这不一定总是如此。amgetbitmapamgettuple 不能在同一个索引扫描中使用;使用 amgetbitmap 时还有其他限制,如 第 63.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 (Relation indexRelation,
                        int nkeys,
                        int norderbys);

估算并返回访问方法执行并行扫描所需的动态共享内存字节数。(此数量是 ParallelIndexScanDescData 中 AM 独立数据所需数量的附加量,而不是替代量。)

nkeysnorderbys 参数指示将在扫描中使用的限定符和排序运算符的数量;这些相同的值将传递给 amrescan。请注意,扫描键的实际值尚未提供。

对于不支持并行扫描的访问方法,或者所需的附加存储字节数为零的情况,没有必要实现此函数。

void
aminitparallelscan (void *target);

当必须重启并行索引扫描时,将调用此函数来重置由 aminitparallelscan 设置的任何共享状态,以便从头开始重启扫描。

对于不支持并行扫描的访问方法,或者在共享内存空间需要无初始化的场景下,没有必要实现此函数。

void
amparallelrescan (IndexScanDesc scan);

如果实现了此函数,将在并行索引扫描开始时调用此函数。 target 将指向至少等于 amestimateparallelscan 之前返回的字节数,并且此函数可以使用该数量的空间来存储它想要的任何数据。

CompareType
amtranslatestrategy (StrategyNumber strategy, Oid opfamily, Oid opcintype);

StrategyNumber
amtranslatecmptype (CompareType cmptype, Oid opfamily, Oid opcintype);

如果实现了此函数,将在并行索引扫描开始时调用此函数。 target 将指向至少等于 amestimateparallelscan 之前返回的字节数,并且此函数可以使用该数量的空间来存储它想要的任何数据。

提交更正

如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符或需要进一步澄清的内容,请使用 此表格 报告文档问题。