Postgresql 中文操作指南
64.2. Index Access Method Functions #
索引访问方法必须在 IndexAmRoutine 中提供的索引构造和维护功能是:
IndexBuildResult *
ambuild (Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo);
构建新索引。索引关系已物理创建,但为空。它必须填充访问方法需要的任何固定数据,以及表中所有现有元组的条目。通常,ambuild 函数将调用 table_index_build_scan() 来扫描表以获取现有元组,并计算需要插入索引的键。此函数必须返回一个已分配帕洛克的结构,其中包含有关新索引的统计信息。
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 指示要执行的唯一性检查的类型。这会因唯一约束是否可延迟而异;有关详细信息,请参见 Section 64.5。通常,访问方法仅在执行唯一性检查时才需要 heapRelation 参数(因为那时它必须查看堆以验证元组生存期)。
indexUnchanged 布尔值提示要编制索引的元组的性质。当它为 true 时,元组是索引中某个现有元组的副本。新元组是一个逻辑上未更改的后继 MVCC 元组版本。当发生 UPDATE 时会出现这种情况,该操作不会修改索引涵盖的任何列,但是仍然需要索引中有一个新版本。索引 AM 可以使用此提示来决定对相同逻辑行大量版本累积的索引部分应用自底向上索引删除。请注意,更新非键列或仅出现在部分索引谓词中的列不会影响 indexUnchanged 的值。核心代码使用低开销方法确定每个元组的 indexUnchanged 值,该方法允许假阳性和假阴性。索引 AM 不能将 indexUnchanged 视为有关元组可见性或版本控制的权威信息源。
此函数的布尔结果值仅在 checkUnique 为 UNIQUE_CHECK_PARTIAL 时才重要。在这种情况下,如果结果为 true,则表示已知新条目是唯一的,而 false 表示它可能不是唯一的(并且必须调度延迟唯一性检查)。对于其他情况,建议返回恒定的 false 结果。
某些索引可能不会索引所有元组。如果元组不应该被索引,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 或返回一个已分配帕洛克的结构,其中包含有关删除操作效果的统计信息。如果不需要将信息传递给 amvacuumcleanup,则返回 NULL 是可以的。
因为 maintenance_work_mem 受限,所以当需要删除大量元组时可能需要多次调用 ambulkdelete。stats 参数是针对此索引的先前调用的结果(它是 VACUUM 操作中的第一次调用时为 NULL)。这使 AM 可以在整个操作中累积统计信息。通常,ambulkdelete 在 stats 不为 null 的情况下会修改并返回相同的结构。
IndexBulkDeleteResult *
amvacuumcleanup (IndexVacuumInfo *info,
IndexBulkDeleteResult *stats);
在 VACUUM 操作之后清理(零个或多个 ambulkdelete 调用)。除了返回索引统计信息之外,这不需要做任何事情,但它可以执行批量清理,例如回收空索引页面。stats 是最后一个 ambulkdelete 调用返回的任何值,或者如果 ambulkdelete 未被调用是因为不需要删除任何元组,则返回 NULL。如果结果不为 NULL 则它必须是一个已分配帕洛克的结构。它所包含的统计信息将用于更新 pg_class,并且如果给出了 VERBOSE,则将由 VACUUM 报告。如果在 VACUUM 操作期间索引根本没有被更改,则返回 NULL 是可以的,但否则应该返回正确的统计信息。
amvacuumcleanup 也将在 ANALYZE 操作完成后被调用。在这种情况下,stats 始终为 NULL 并且将忽略任何返回值。可以通过检查 info→analyze_only 来区分这种情况。建议访问方法在这个调用中除了进行插入后清理操作之外什么都不做,并且仅在自动清理工作进程中进行。
bool
amcanreturn (Relation indexRelation, int attno);
通过返回给定列的原始索引值,检查索引是否可以对列支持 index-only scans 。属性编号从 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);
估算索引扫描的成本。此函数在 Section 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。否则,当 index_oid 和 attno 都为 pg_indexam_has_property 调用为 0,或者当 index_oid 有效且 attno 为 pg_index_has_property 调用为 0,或者当 index_oid 有效且 attno 大于 pg_index_column_has_property 调用中的 0 时,将调用 amproperty 方法。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
当然,索引的目的是支持扫描与可索引 WHERE 条件(通常称为 qualifier 或 scan key)匹配的元组。索引扫描的语义在 Section 64.3 中进行了更全面的描述,它在下面。索引访问方法可以支持“纯”索引扫描,“位图”索引扫描或两者兼而有之。索引访问方法必须或可能提供的与扫描相关的函数是:
IndexScanDesc
ambeginscan (Relation indexRelation,
int nkeys,
int norderbys);
准备索引扫描。 nkeys 和 norderbys 参数分别表示扫描中将使用的限定符和排序运算符的数量,这些在空间分配方面可能有帮助。需要注意的是,尚未提供扫描键的实际值。结果必须是 palloc 后的结构。出于实现方面的考虑,索引访问方法 must 通过调用 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 调用方绝不会重新检查部分索引谓词(如果有)。
如果索引支持 index-only scans(即,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 的数据时使用。)在任一情况下,对指针引用的数据的管理都是访问方法的责任。该数据至少在下次 amgettuple、amrescan 或针对扫描的 amendscan 调用之前保持良好状态。
只有在访问方法支持“普通”索引扫描时才需要提供 amgettuple 函数。如果它不支持,则其 IndexAmRoutine 结构中的 amgettuple 字段必须设置为 NULL。
int64
amgetbitmap (IndexScanDesc scan,
TIDBitmap *tbm);
抓取给定扫描中的所有元组,并将它们添加到调用者提供的 TIDBitmap(即,将元组 ID 集合 OR 到位图中已经存在的任何集合中)。将返回抓取的元组数(这可能只是一个近似计数,例如,某些 AM 不会检测重复)。在将元组 ID 插入位图时,_amgetbitmap_可以指明需要对特定元组 ID 重新检查扫描条件。这类似于 _amgettuple_的 _xs_recheck_输出参数。注意:在当前实现中,对该功能的支持与对位图本身的无损存储的支持是混杂在一起的,因此调用者重新检查了扫描条件和部分索引谓词(如果有的话),以进行可重新检查的元组。但并非总是如此。_amgetbitmap_和 _amgettuple_不能在同一索引扫描中使用;在使用 _amgetbitmap_时也有其他限制,如 Section 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。
除了支持普通索引扫描之外,某些类型的索引可能希望支持 parallel index scans,它允许多个后端协同执行索引扫描。索引访问方法应安排所有事务,以便每个协同进程返回普通非并行索引扫描将执行的元组子集,但要求这些子集的并集等于普通非并行索引扫描将返回的元组集。此外,虽然由并行扫描返回的元组不一定要有全局顺序,但是每个协同后端内返回的元组子集的顺序必须与请求的顺序匹配。以下函数可用于支持并行索引扫描:
Size
amestimateparallelscan (void);
估计并返回访问方法执行并行扫描所需的动态共享内存字节数。(此数字是对 ParallelIndexScanDescData 中 AM 独立数据的所需空间的补充,而不是替代。)
对于不支持并行扫描或所需额外存储字节数为零的访问方法,不必实现此函数。
void
aminitparallelscan (void *target);
在并行扫描开始时,将调用此函数以初始化动态共享内存。target 将至少指向之前由 amestimateparallelscan 返回的字节数,并且此函数可以使用该数量的空间来存储它希望的任何数据。
对于不支持并行扫描的访问方法,或在不需要初始化共享内存空间的情况下,不必实现此函数。
void
amparallelrescan (IndexScanDesc scan);
如果必须重新启动并行索引扫描,则会调用此函数(如果已实现)。它应重置 aminitparallelscan 设置的任何共享状态,以便扫描从头开始重新启动。