2024 年 9 月 26 日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不受支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1

62.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 指示要执行的唯一性检查类型。这取决于唯一约束是否可延迟;有关详细信息,请参阅 第 62.5 节。通常,访问方法仅在执行唯一性检查时需要 heapRelation 参数(因为那时它将必须查看堆以验证元组的生存性)。

indexUnchanged 布尔值提供了有关要索引的元组性质的提示。当它为 true 时,该元组是索引中某个现有元组的副本。新元组是逻辑上不变的后继 MVCC 元组版本。当发生 UPDATE 时会发生这种情况,该 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 函数必须按照 callback(TID, callback_state) returns bool 的风格调用,以确定由其引用的 TID 标识的任何特定索引条目是否要被删除。必须返回 NULL 或一个包含删除操作效果统计信息的 palloc'd 结构。如果不需要将任何信息传递给 amvacuumcleanup,则返回 NULL 也可以。

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

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

清理 VACUUM 操作后的操作(零个或多个 ambulkdelete 调用)。这除了返回索引统计信息之外不需要做任何事情,但它可能会执行批量清理,例如回收空索引页。 stats 是最后一个 ambulkdelete 调用返回的任何内容,或者如果不需要删除任何元组,则为 NULL。如果结果不为 NULL,它必须是一个 palloc'd 结构。它包含的统计信息将用于更新 pg_class,如果给出了 VERBOSE,则将由 VACUUM 报告。如果在 VACUUM 操作期间索引根本没有更改,则返回 NULL 也可以,但应返回正确的统计信息。

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

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);

估计索引扫描的成本。此函数将在下面 第 62.6 节 中完整描述。

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

解析并验证索引的 reloptions 数组。这仅在索引存在非空 reloptions 数组时调用。 reloptions 是一个 text 数组,包含 name=value 形式的条目。该函数应构造一个 bytea 值,该值将被复制到索引的 relcache 条目的 rd_options 字段中。 bytea 值的数据内容由访问方法定义;大多数标准访问方法使用结构 StdRdOptions。当 validate 为 true 时,如果任何选项无法识别或具有无效值,该函数应报告适当的错误消息;当 validate 为 false 时,无效条目应被静默忽略。(validate 在加载已存储在 pg_catalog 中的选项时为 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。否则,amproperty 方法将被调用,对于 pg_indexam_has_property 调用,index_oidattno 都是零;对于 pg_index_has_property 调用,index_oid 有效,attno 为零;对于 pg_index_column_has_property 调用,index_oid 有效,attno 大于零。 prop 是一个枚举值,标识正在测试的属性,而 propname 是原始属性名称字符串。如果核心代码不识别属性名称,则 propAMPROP_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 消息报告,通常在 INFO 级别。

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

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

当然,索引的目的是支持对匹配可索引 WHERE 条件的元组的扫描,通常称为 限定符扫描键。索引扫描的语义在下面 第 62.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);

启动或重新启动索引扫描,可能使用新的扫描键。(要使用之前传递的键重新启动,请将 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 对其任何列返回 true),则在成功的情况下,AM 还必须检查 scan->xs_want_itup,如果该值为 true,则必须返回索引条目最初索引的数据。对于 amcanreturn 返回 false 的列,可以返回为 null。数据可以以存储在 scan->xs_itup 处的 IndexTuple 指针的形式返回,元组描述符为 scan->xs_itupdesc;或者以存储在 scan->xs_hitup 处的 HeapTuple 指针的形式返回,元组描述符为 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 时,还有其他限制,如 第 62.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 (int nkeys,
                        int norderbys);

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

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

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

void
aminitparallelscan (void *target);

此函数将在并行扫描开始时被调用以初始化动态共享内存。target 将指向至少由 amestimateparallelscan 返回的字节数,此函数可以使用该空间存储任何它想要的数据。

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

void
amparallelrescan (IndexScanDesc scan);

如果实现,此函数将在必须重新启动并行索引扫描时被调用。它应该重置由 aminitparallelscan 设置的任何共享状态,以便扫描将从头开始重新启动。

提交更正

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