Redrock Postgres 搜索 英文
版本: 9.3 / 9.4 / 9.5 / 9.6 / 10 / 11 / 12 / 13 / 14 / 15 / 16 / 17

62.2. 索引访问方法函数 #

IndexAmRoutine 中,索引访问方法必须提供的索引构建和维护函数如下:

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

构建一个索引。索引关系已物理创建,但为空。必须使用访问方法所需的所有固定数据填充它,再添加表中已有的所有元组的条目。通常,ambuild 函数将调用 table_index_build_scan() 来扫描现有元组的表并计算需要插入索引的键。函数必须返回一个分配的包含新索引统计信息的结构。 amcanbuildparallel 标志指示访问方法是否支持并行索引构建。当设置为 true 时,系统将尝试为构建分配并行工作人员。仅支持非并行索引构建的访问方法应将此标志设置为 false

void
ambuildempty (Relation indexRelation);

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

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

将新元组插入现有索引。 valuesisnull 数组给出要编制索引的键值,heap_tid 是要编制索引的 TID。如果访问方法支持唯一索引(其 amcanunique 标志为真),则 checkUnique 指示要执行的唯一性检查类型。这根据唯一约束是否可延期而有所不同;见第 62.5 节以了解详情。通常,只有在执行唯一性检查时访问方法才需要 heapRelation 参数(那时它必须检查堆以验证元组生存性)。

布尔值 indexUnchanged 给出了关于要编入索引的元组本质的提示。当其为真时,元组是索引中某个现有元组的副本。新元组是一个逻辑上没有更改的后继 MVCC 元组版本。这会发生在 UPDATE 不修改索引所覆盖的任何列,但仍然需要在索引中提供新版本时。索引 AM 可能会使用此提示来决定对同一逻辑行有许多版本的索引的部分应用自底向上索引删除。注意,更新非关键列或仅出现在部分索引谓词中的列不会影响 indexUnchanged 的值。核心代码通过允许出现误报和漏报的低开销方法确定每个元组的 indexUnchanged 值。索引 AM 绝对不能将 indexUnchanged 视为有关元组可见性或版本控制的信息的权威来源。

仅当 checkUniqueUNIQUE_CHECK_PARTIAL 时,函数的布尔结果值才有意义。在这种情况下,真结果意味着已知新条目是唯一的,而假结果意味着它可能是非唯一的(必须安排延迟的唯一性检查)。对于其他情况,则建议使用常量假结果。

一些索引可能不编入索引中所有的元组。如果元组未编入索引,则 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 或包含有关删除操作的效果的统计信息的已分配结构。如果没有信息需要传递给 amvacuumcleanup 则返回 NULL 即可。

由于有限的 maintenance_work_mem,在删除许多元组时可能需要多次调用 ambulkdeletestats 参数是对此索引上一次调用得到的 result (在 VACUUM 操作中的第一次调用时,它为 NULL)。这允许 AM 积累整个操作过程的统计信息。通常情况下,如果传入的 stats 不为空,ambulkdelete 将修改并返回同一 struct。

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

VACUUM 操作(零次或多次 ambulkdelete 调用)之后进行清理。这不必做其他任何事情,只需返回索引统计信息即可,但它可能执行大规模清理,例如回收空的索引页面。 stats 是最后一个 ambulkdelete 调用返回的任何内容,或者如果未调用 ambulkdelete(因为不需要删除任何元组)则为 NULL。如果结果不为 NULL,它必须是 palloc'd struct。它包含的统计信息将用于更新 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 struct 中将 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 时,应忽略无效的条目。(当加载已存储在 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。否则,amproperty 方法将被调用,其中 index_oidattno 均为零用于 pg_indexam_has_property 调用,或者 index_oid 有效且 attno 为零用于 pg_index_has_property 调用,或者 index_oid 有效且 attno 大于零用于 pg_index_column_has_property 调用。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 参数是 amapi.h 中定义的 OpFamilyMember 结构的列表。此函数执行的测试通常是 amvalidate 执行的测试的子集,因为 amadjustmembers 无法假设它看到一组完整的成员。例如,检查支持函数的签名是合理的,但检查是否提供了所有必需的支持函数是不合理的。可以通过抛出错误来报告任何问题。OpFamilyMember 的依赖相关字段由核心代码初始化,以创建对操作符类的硬依赖(如果这是 CREATE OPERATOR CLASS),或对 operator family 的软依赖(如果这是 ALTER OPERATOR FAMILY ADD)。如果其他一些行为更合适,amadjustmembers 可以调整这些字段。例如,GIN、GiST 和 SP-GiST 总会将操作符成员设置为对操作符家族具有软依赖,因为在这些索引类型中操作符与操作符类之间的连接相对较弱;所以允许自由地添加和移除操作符成员是合理的。可选的支持函数通常也会获得软依赖,以便在必要时可以移除它们。

当然,索引的目的是支持对匹配可索引 WHERE 条件(通常称为限定符扫描键)的元组的扫描。在下面的 第 62.3 节 中更全面地描述了索引扫描的语义。索引访问方法可以支持普通索引扫描、位图索引扫描或两者兼有。索引访问方法必须或可以提供的扫描相关函数有

IndexScanDesc
ambeginscan (Relation indexRelation,
             int nkeys,
             int norderbys);

准备扫描索引。nkeysnorderbys 参数表明在扫描中将使用的限定符和排序运算符的数量;这些信息对于空间分配目的可能很有用。请注意,尚未提供扫描键的实际值。结果必须是一个已分配的结构。由于实现原因,索引访问方法必须通过调用 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 的数据时应使用后者格式。)在任何情况下,由访问方法负责管理由指针引用的数据。该数据必须至少保留到对该扫描执行下一个 amgettupleamrescanamendscan 调用之前。

仅当访问方法支持plain索引扫描时,才需要提供 amgettuple 函数。如果不支持,则其 IndexAmRoutine 结构中的 amgettuple 字段必须设置为 NULL。

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

获取给定扫描中的所有元组,并将它们添加到调用方提供的 TIDBitmap 中(即,将元组 ID 集 OR 到已在位图中的任何集合)。返回已获取的元组数(这可能只是一个近似计数,例如,某些 AM 不会检测重复项)。在向位图中插入元组 ID 时,amgetbitmap 可以指示需要对特定元组 ID 重新检查扫描条件。这与 amgettuplexs_recheck 输出参数类似。注意:在当前实现中,对该功能支持与对位图本身有损存储的支持相结合,因此调用方重新检查重新检查元组的扫描条件和部分索引谓词(如果有)。然而,这可能并不总是成立。amgetbitmapamgettuple 不能在同一个索引扫描中使用;在使用 amgetbitmap 时还有其他限制,如 第 62.3 节 中所述。

仅当访问方法支持bitmap索引扫描时,才需要提供 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 设置的所有共享状态,以便从开头重新开始扫描。