支持的版本: 当前 (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

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 时,不会修改索引覆盖的任何列,但仍然需要在索引中创建一个新版本时,就会发生这种情况。索引 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,当要删除许多元组时,可能需要多次调用 ambulkdeletestats 参数是此索引的先前调用的结果(在 VACUUM 操作中的第一次调用为 NULL)。这允许 AM 在整个操作中累积统计信息。通常,如果传递的 stats 不为 null,则 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 来区分这种情况。建议访问方法在此类调用中除了插入后清理之外不做任何操作,并且仅在自动清理工作进程中进行。

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 为真时,如果任何选项无法识别或具有无效值,该函数应报告适当的错误消息;当 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 和相关函数的默认行为。如果访问方法对索引属性查询没有任何特殊行为,则可以在其 IndexAmRoutine 结构体中将 amproperty 字段设置为 NULL。否则,对于 pg_indexam_has_property 调用,将使用 index_oidattno 都为零来调用 amproperty 方法;对于 pg_index_has_property 调用,将使用有效的 index_oidattno 为零来调用 amproperty 方法;对于 pg_index_column_has_property 调用,将使用有效的 index_oid 和大于零的 attno 来调用 amproperty 方法。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 函数必须在 opclass 无效时返回 false。问题应通过 ereport 消息报告,通常在 INFO 级别。

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

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

索引的目的当然是支持扫描匹配可索引 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。)请注意,键或 order-by 运算符的数量不得大于传递给 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_itupIndexTuple 指针的形式返回,元组描述符为 scan->xs_itupdesc;或以存储在 scan->xs_hitupHeapTuple 指针的形式返回,元组描述符为 scan->xs_hitupdesc。(后一种格式应用于重建可能无法放入 IndexTuple 的数据。)在任何一种情况下,指针引用的数据的管理都由访问方法负责。数据必须至少在下一次调用 amgettupleamrescanamendscan 之前保持良好状态。

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

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

获取给定扫描中的所有元组,并将它们添加到调用者提供的 TIDBitmap(即,将元组 ID 集合或到位图中已有的集合)。返回获取的元组数(这可能只是一个近似计数,例如,某些 AM 不会检测重复项)。在将元组 ID 插入到位图时,amgetbitmap 可以指示需要重新检查特定元组 ID 的扫描条件。这类似于 amgettuplexs_recheck 输出参数。注意:在当前实现中,对该功能的支持与对位图本身有损存储的支持相混淆,因此调用者会重新检查扫描条件和可重新检查的元组的部分索引谓词(如果有)。但是,这可能并不总是正确的。amgetbitmapamgettuple 不能在同一个索引扫描中使用;使用 amgetbitmap 时还有其他限制,如第 62.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 (int nkeys,
                        int norderbys);

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

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

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

void
aminitparallelscan (void *target);

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

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

void
amparallelrescan (IndexScanDesc scan);

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

提交更正

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