61.2. 索引访问方法功能

索引访问方法必须在IndexAmRoutine中提供的索引构造和维护功能是:

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

构建一个新索引。索引关系已经物理创建,但是为空。必须使用访问方法所需的任何固定数据以及表中已存在的所有 Tuples 的条目来填充它。通常,ambuild函数将调用IndexBuildHeapScan()来扫描表中的现有 Tuples,并计算需要插入索引中的键。该函数必须返回一个包含有关新索引统计信息的 palloc 结构。

void
ambuildempty (Relation indexRelation);

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

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

将新的 Tuples 插入现有索引。 valuesisnull数组提供要索引的键值,而heap_tid是要索引的 TID。如果访问方法支持唯一索引(其amcanunique标志为 true),则checkUnique指示要执行的唯一性检查的类型。这取决于唯一约束是否可延期。有关详情,请参见Section 61.5。通常,访问方法在执行唯一性检查时仅需要heapRelation参数(因为它随后将必须查看堆以验证 Tuples 活动性)。

仅当checkUniqueUNIQUE_CHECK_PARTIAL时,函数的布尔结果值才有意义。在这种情况下,结果为 true 表示新条目是唯一的,而 false 则为新条目(必须安排延迟的唯一性检查)。对于其他情况,建议始终提供错误的结果。

一些索引可能不会索引所有 Tuples。如果不对 Tuples 构建索引,则aminsert应该不做任何事情就返回。

如果索引 AM 希望在 SQL 语句内的连续索引插入之间缓存数据,则可以在indexInfo->ii_Context中分配空间,并在indexInfo->ii_AmCache中存储指向数据的指针(最初为 NULL)。

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

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

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

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

进行VACUUM次操作(零次或多次ambulkdelete次调用)后进行清理。除了返回索引统计信息外,此操作不需要执行任何其他操作,但是它可能会执行批量清理,例如回收空的索引页。 stats是最后一个ambulkdelete调用返回的值,如果未调用ambulkdelete是因为不需要删除 Tuples 则返回 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);

通过以IndexTuple的形式返回索引条目的索引列值,检查索引是否可以在给定列上支持index-only scans。属性号基于 1,即第一列的 attno 为 1.如果支持,则返回 true,否则返回 false。如果访问方法根本不支持仅索引扫描,则可以将其IndexAmRoutine结构中的amcanreturn字段设置为 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值的数据内容打开以供访问方法定义;大多数标准访问方法都使用 struct 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。否则,将以* index_oid attno 都为零来调用amproperty方法,对于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,则核心代码将 continue 使用其正常逻辑来确定属性测试结果。

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

bool
amvalidate (Oid opclassoid);

验证指定操作员类的目录条目,只要访问方法可以合理地做到这一点即可。例如,这可能包括测试是否提供了所有必需的支持功能。如果 opclass 无效,则amvalidate函数必须返回 false。应该用ereport条消息报告问题。

当然,索引的目的是支持对匹配可索引WHERE条件(通常称为* qualifier scan key *)的 Tuples 进行扫描。索引扫描的语义在下面的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的数量。实际上,当通过嵌套循环联接选择新的外部 Tuples 时,使用重新启动功能,因此需要新的密钥比较值,但是扫描密钥结构保持不变。

boolean
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

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

如果索引支持index-only scans(即amcanreturn为其返回 true),则成功后 AM 还必须检查scan->xs_want_itup,如果为 true,则它必须返回索引条目的原始索引数据。可以以存储在scan->xs_itupIndexTuple指针的形式以及 TuplesDescriptorsscan->xs_itupdesc返回数据。或以HeapTuple指针的形式存储在scan->xs_hitup,并带有 TuplesDescriptorsscan->xs_hitupdesc。 (在重建可能不适合IndexTuple的数据时,应使用后一种格式.)在两种情况下,访问方法的职责都是 Management 指针引用的数据。数据必须至少在下一个amgettupleamrescanamendscan进行扫描之前保持良好状态。

仅当访问方法支持“普通”索引扫描时才需要提供amgettuple功能。如果不是,则其IndexAmRoutine结构中的amgettuple字段必须设置为 NULL。

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

提取给定扫描中的所有 Tuples,并将其添加到调用方提供的TIDBitmap中(即,将 TuplesID 的集合与位图中已经存在的任何 Tuples 进行或运算)。返回获取的 Tuples 数(这可能只是一个近似计数,例如,某些 AM 未检测到重复)。在将 TuplesID 插入位图中时,amgetbitmap可以指示特定 TuplesID 需要重新检查扫描条件。这类似于amgettuplexs_recheck输出参数。注意:在当前实现中,对此功能的支持与对位图本身的有损存储的支持混为一谈,因此,调用方会重新检查扫描条件和部分索引谓词(如果有),以检查可重复的 Tuples。但是,这可能并不总是正确的。 amgetbitmapamgettuple不能用于同一索引扫描;如Section 61.3所述,在使用amgetbitmap时也有其他限制。

仅当访问方法支持“位图”索引扫描时才需要提供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。

除了支持普通索引扫描外,某些类型的索引可能还希望支持并行索引扫描,这允许多个后端配合执行索引扫描。索引访问方法应该安排事物,以便每个协作过程都返回 Tuples 的子集,该子集将通过普通的非并行索引扫描执行,但方式是这些子集的并集等于 Tuples 集合将由普通的非并行索引扫描返回。此外,尽管并行扫描不需要返回任何 Tuples 的全局 Sequences,但是在每个协作后端内返回的 Tuples 子集的 Sequences 必须与请求的 Sequences 匹配。可以实现以下功能以支持并行索引扫描:

Size
amestimateparallelscan (void);

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

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

void
aminitparallelscan (void *target);

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

对于不支持并行扫描的访问方法,或者在所需的共享内存空间不需要初始化的情况下,无需实现此功能。

void
amparallelrescan (IndexScanDesc scan);

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