64.2. 索引访问方法函数#
索引访问方法必须在IndexAmRoutine
中提供的索引构建和维护函数是
IndexBuildResult *
ambuild (Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo);
构建新索引。索引关系已物理创建,但为空。必须用访问方法所需的所有固定数据以及表中已存在的所有元组的条目来填充它。通常,ambuild
函数将调用table_index_build_scan()
来扫描表以查找现有元组,并计算需要插入到索引中的键。该函数必须返回一个包含有关新索引的统计信息的 palloc'd 结构。
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);
将新元组插入现有索引中。values
和isnull
数组给出要编入索引的关键值,heap_tid
是要编入索引的 TID。如果访问方法支持唯一索引(其amcanunique
标志为 true),则checkUnique
指示要执行的唯一性检查类型。这取决于唯一约束是否可推迟;有关详细信息,请参见第 64.5 节。通常,访问方法仅在执行唯一性检查时才需要heapRelation
参数(因为那时它必须查看堆以验证元组的活动性)。
indexUnchanged
布尔值给出了有关要编入索引的元组的性质的提示。当它为 true 时,元组是索引中某些现有元组的副本。新元组是逻辑上未更改的后继 MVCC 元组版本。当发生不修改索引覆盖的任何列的UPDATE
时,但仍然需要在索引中使用新版本时,就会发生这种情况。索引 AM 可能会使用此提示来决定在同一逻辑行的许多版本累积的索引部分中应用自下而上的索引删除。请注意,更新非键列或仅出现在部分索引谓词中的列不会影响indexUnchanged
的值。核心代码使用低开销方法确定每个元组的indexUnchanged
值,该方法允许出现误报和漏报。索引 AM 不得将indexUnchanged
视为有关元组可见性或版本控制的权威信息源。
仅当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(*
TID*, callback_state) returns bool
的样式调用传入的callback
函数,以确定是否要删除由其引用的 TID 标识的任何特定索引条目。必须返回 NULL 或包含有关删除操作影响的统计信息的 palloc'd 结构。如果没有信息需要传递给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'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);
估算索引扫描的成本。此函数在第 64.6 节中进行了详细描述。
bytea *
amoptions (ArrayType *reloptions,
bool validate);
解析和验证索引的 reloptions 数组。仅当索引存在非空 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。否则,对于pg_indexam_has_property
调用,将使用*index_oid
和attno
均为零来调用amproperty
方法;对于pg_index_has_property
调用,将使用有效的index_oid
和零attno
来调用该方法;对于pg_index_column_has_property
调用,将使用有效的index_oid
和大于零的attno
来调用该方法。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);
验证指定运算符类的目录条目,只要访问方法可以合理地执行此操作。例如,这可能包括测试是否提供了所有必需的支持函数。如果 opclass 无效,则amvalidate
函数必须返回 false。问题应通过ereport
消息报告,通常在INFO
级别。
void
amadjustmembers (Oid opfamilyoid,
Oid opclassoid,
List *operators,
List *functions);
验证运算符系列中建议的新运算符和函数成员,只要访问方法可以合理地执行此操作,并在默认值不令人满意时设置其依赖类型。这在CREATE OPERATOR CLASS
和ALTER OPERATOR FAMILY ADD
期间调用;在后一种情况下,*opclassoid
*为InvalidOid
。List
参数是OpFamilyMember
结构的列表,如amapi.h
中定义的。此函数执行的测试通常是amvalidate
执行的测试的子集,因为amadjustmembers
无法假设它看到的是一组完整的成员。例如,检查支持函数的签名是合理的,但检查是否提供了所有必需的支持函数是不合理的。可以通过抛出错误来报告任何问题。如果这是CREATE OPERATOR CLASS
,则核心代码将OpFamilyMember
结构的依赖相关字段初始化为对 opclass 创建硬依赖;如果这是ALTER OPERATOR FAMILY ADD
,则初始化为对 opfamily 创建软依赖。如果其他一些行为更合适,amadjustmembers
可以调整这些字段。例如,GIN、GiST 和 SP-GiST 总是将运算符成员设置为对 opfamily 具有软依赖,因为在这些索引类型中,运算符和 opclass 之间的连接相对较弱;因此,允许自由添加和删除运算符成员是合理的。可选支持函数通常也具有软依赖,以便在必要时可以删除它们。
当然,索引的目的是支持扫描与可索引的WHERE
条件(通常称为限定符或扫描键)匹配的元组。索引扫描的语义在下面的第 64.3 节中有更全面的描述。索引访问方法可以支持“普通”索引扫描、“位图”索引扫描或两者。索引访问方法必须或可能提供的与扫描相关的函数是
IndexScanDesc
ambeginscan (Relation indexRelation,
int nkeys,
int norderbys);
准备索引扫描。nkeys
和norderbys
参数指示扫描中将使用的条件和排序运算符的数量;它们可能对空间分配目的有用。请注意,尚未提供扫描键的实际值。结果必须是分配的结构。出于实现原因,索引访问方法必须通过调用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
的数据时,应使用后一种格式。)在任何情况下,对指针引用的数据进行管理都是访问方法的责任。数据至少在扫描的下一个amgettuple
、amrescan
或amendscan
调用之前保持良好状态。
仅当访问方法支持“普通”索引扫描时,才需要提供amgettuple
函数。如果不支持,则其IndexAmRoutine
结构中的amgettuple
字段必须设置为 NULL。
int64
amgetbitmap (IndexScanDesc scan,
TIDBitmap *tbm);
获取给定扫描中的所有元组,并将它们添加到调用者提供的TIDBitmap
(即,将元组 ID 集或到位图中已有的任何集合中)。返回获取的元组数(这可能只是一个近似计数,例如,某些 AM 不会检测重复项)。在将元组 ID 插入位图时,amgetbitmap
可以指示需要对特定元组 ID 重新检查扫描条件。这类似于amgettuple
的xs_recheck
输出参数。注意:在当前实现中,对该功能的支持与对位图本身的无损存储的支持混为一谈,因此调用者会重新检查可重新检查元组的扫描条件和部分索引谓词(如果有)。然而,这可能并不总是正确的。amgetbitmap
和amgettuple
不能在同一个索引扫描中使用;在使用amgetbitmap
时还有其他限制,如第 64.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
中与 AM 无关的数据所需的空间,而不是代替它。)
对于不支持并行扫描或所需附加存储字节数为零的访问方法,无需实现此函数。
void
aminitparallelscan (void *target);
此函数将在并行扫描开始时被调用以初始化动态共享内存。*target
*将至少指向amestimateparallelscan
之前返回的字节数,此函数可以使用该空间存储它希望的任何数据。
对于不支持并行扫描或所需共享内存空间无需初始化的访问方法,无需实现此函数。
void
amparallelrescan (IndexScanDesc scan);
如果实现了此函数,则在必须重新启动并行索引扫描时将调用此函数。它应重置aminitparallelscan
设置的任何共享状态,以便从头开始重新启动扫描。