pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
索引访问方法必须提供如下索引构建和维护函数:
IndexBuildResult *
ambuild (Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo);
构建新索引。索引关系已在物理上创建,但内容为空。必须填入访问方法所需的固定数据,以及表中所有已有元组对应的条目。通常,ambuild函数会调用IndexBuildHeapScan()扫描表中现有的元组,并计算需要插入索引的键。函数必须返回一个通过 palloc 分配的结构体,其中包含新索引的统计信息。
void ambuildempty (Relation indexRelation);
构建一个空索引,并将其写入给定关系的初始化分支(INIT_FORKNUM)。只有不记录 WAL 的表才会调用此方法;写入初始化分支的空索引会在每次服务器重启时复制到主关系分支上。
bool
aminsert (Relation indexRelation,
Datum *values,
bool *isnull,
ItemPointer heap_tid,
Relation heapRelation,
IndexUniqueCheck checkUnique);
将一个新元组插入已有索引。values和isnull数组给出要索引的键值,heap_tid则是要索引的 TID。如果访问方法支持唯一索引(其pg_am.amcanunique标志为 true),则checkUnique指明要执行的唯一性检查类型,具体取决于唯一约束是否可延迟;详情参见第 58.5 节。通常,访问方法只在执行唯一性检查时才需要heapRelation参数(因为此时必须查看堆以确认元组是否存活)。
只有当checkUnique为UNIQUE_CHECK_PARTIAL时,该函数的布尔返回值才有意义。在这种情况下,TRUE 表示已知新条目唯一,而 FALSE 表示它可能不唯一(并且必须安排一次延迟唯一性检查)。对于其他情况,建议始终返回 FALSE。
某些索引可能不会为所有元组建立索引。如果某个元组不应被索引,aminsert 应当直接返回而不做任何事。
IndexBulkDeleteResult *
ambulkdelete (IndexVacuumInfo *info,
IndexBulkDeleteResult *stats,
IndexBulkDeleteCallback callback,
void *callback_state);
从索引中删除元组。这是一个“批量删除”操作,旨在通过扫描整个索引并检查每个条目是否应被删除来实现。必须调用传入的 callback 函数,其调用形式为 callback(,以确定由其引用 TID 标识的某个索引条目是否应删除。该函数必须返回 NULL,或者返回一个通过 palloc 分配的结构体,其中包含此次删除操作影响的统计信息。如果不需要向 TID, callback_state) returns boolamvacuumcleanup 传递信息,返回 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 分配的结构体。其中的统计信息将用于更新 pg_class,并在指定了 VERBOSE 时由 VACUUM 报告。如果索引在整个 VACUUM 操作期间完全没有变化,返回 NULL 也是可以的;否则应返回正确的统计信息。
从PostgreSQL 8.4 开始,amvacuumcleanup 也会在 ANALYZE 操作完成时被调用。在这种情况下,stats 总是 NULL,而且任何返回值都会被忽略。这种情况可以通过检查 info->analyze_only 来区分。我们建议访问方法在这种调用中除了做插入后的清理外什么都不做,而且只在自动清理工作进程中这样做。
bool amcanreturn (Relation indexRelation, int attno);
检查索引能否通过以 IndexTuple 形式返回某个索引条目的被索引列值,在给定列上支持仅索引扫描。属性编号从 1 开始,也就是说第一列的 attno 为 1。若支持则返回 TRUE,否则返回 FALSE。如果访问方法根本不支持仅索引扫描,那么pg_am行中的amcanreturn字段可以设为零。
void
amcostestimate (PlannerInfo *root,
IndexPath *path,
double loop_count,
Cost *indexStartupCost,
Cost *indexTotalCost,
Selectivity *indexSelectivity,
double *indexCorrelation,
double *indexPages);
估计一次索引扫描的代价。该函数将在后面的 第 58.6 节 中详细讨论。
bytea *
amoptions (ArrayType *reloptions,
bool validate);
解析并验证索引的 reloptions 数组。只有当该索引存在非 NULL 的 reloptions 数组时才会调用此函数。reloptions 是一个 text 数组,其中的条目形如 name=value。该函数应构造一个 bytea 值,并将其复制到索引 relcache 条目的 rd_options 字段中。这个 bytea 值中的数据内容由访问方法自行定义;大多数标准访问方法使用结构体 StdRdOptions。当 validate 为真时,若存在未识别选项或无效取值,函数应报告合适的错误消息;当 validate 为假时,无效条目应被静默忽略。(当装载已存储在 pg_catalog 中的选项时,validate 为假;此时只有在访问方法改变了选项规则时才可能发现无效条目,而忽略过时条目是合适的。)如果希望采用默认行为,返回 NULL 也是可以的。
索引的目的当然是支持扫描那些匹配可索引 WHERE 条件的元组,这种条件常被称为限定词或扫描键。关于索引扫描的语义,将在下面的 第 58.3 节 中更详细地说明。一种索引访问方法可以支持“普通”索引扫描、“位图”索引扫描,或者两者都支持。索引访问方法必须或可以提供的扫描相关函数如下:
IndexScanDesc
ambeginscan (Relation indexRelation,
int nkeys,
int norderbys);
为一次索引扫描做准备。nkeys 和 norderbys 参数表示扫描中将使用的限定条件和排序操作符数量,这些信息可能有助于空间分配。请注意,此时还没有提供扫描键的实际值。结果必须是一个通过 palloc 分配的结构体。出于实现上的原因,索引访问方法必须通过调用 RelationGetIndexScan() 来创建这个结构体。大多数情况下,ambeginscan 除了做这次调用以及也许获取一些锁之外,不会做太多工作;索引扫描启动中真正有意思的部分在 amrescan 中。
void
amrescan (IndexScanDesc scan,
ScanKey keys,
int nkeys,
ScanKey orderbys,
int norderbys);
开始或重新开始一次索引扫描,并且可以使用新的扫描键。(若要用之前传入的键重新开始,则为 keys 和/或 orderbys 传递 NULL。)请注意,键的数量或排序操作符的数量都不允许超过传给 ambeginscan 的数量。实践中,这种重启特性通常用于嵌套循环连接选中了新的外层元组,因此需要新的键比较值,但扫描键结构体本身保持不变的场景。
boolean
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;若其为真,就必须以如下形式返回该索引条目的原始被索引数据:把 IndexTuple 指针存入 scan->xs_itup,其元组描述符为 scan->xs_itupdesc。(指针所引用数据的管理责任在访问方法一侧。这份数据必须至少保持有效,直到该扫描下一次调用 amgettuple、amrescan 或 amendscan。)
只有当访问方法支持“普通”索引扫描时,才需要提供 amgettuple 函数。如果不支持,其 pg_am 行中的 amgettuple 字段必须设为零。
int64
amgetbitmap (IndexScanDesc scan,
TIDBitmap *tbm);
取出给定扫描中的所有元组,并将其加入调用者提供的 TIDBitmap 中(也就是把这组元组 ID 与位图中已有的集合做 OR)。返回值是取得的元组数量(这可能只是近似计数,例如某些 AM 不会检测重复项)。在把元组 ID 插入位图时,amgetbitmap 可以指出某些具体的元组 ID 需要重新检查扫描条件。这类似于 amgettuple 的 xs_recheck 输出参数。注意:在当前实现中,对这一特性的支持与位图自身的有损存储支持混在一起,因此调用者会对需要重检的元组同时重新检查扫描条件和部分索引谓词(如果有)。不过,这并不一定永远如此。amgetbitmap 和 amgettuple 不能在同一次索引扫描中同时使用;使用 amgetbitmap 时还有其他限制,详见 第 58.3 节。
只有当访问方法支持“位图”索引扫描时,才需要提供 amgetbitmap 函数。如果不支持,其 pg_am 行中的 amgetbitmap 字段必须设为零。
void amendscan (IndexScanDesc scan);
结束一次扫描并释放资源。scan 结构体本身不应被释放,但必须释放访问方法内部获取的所有锁、解除所有钉住状态,并释放由 ambeginscan 和其他扫描相关函数分配的其他内存。
void ammarkpos (IndexScanDesc scan);
标记当前扫描位置。访问方法只需要为每次扫描支持一个被记住的扫描位置。
void amrestrpos (IndexScanDesc scan);
将扫描恢复到最近一次标记的位置。
按照惯例,索引访问方法函数的 pg_proc 条目应显示正确的参数个数,但把所有参数都声明为 internal 类型(因为大多数参数的类型对 SQL 而言是未知的,而且我们也不希望用户直接调用这些函数)。返回类型根据实际情况声明为 void、internal 或 boolean。唯一的例外是 amoptions,它应当正确地声明为接受 text[] 和 bool 并返回 bytea。这一规定允许客户端代码执行 amoptions 来测试选项设置的有效性。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。