54.2. 索引访问方法函数

索引访问方法必须提供的索引构造和维护函数有:

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

创建一个新索引。索引关系已经物理上创建好了,但是是空的。 必须用索引访问方法要求的固定数据和代表所有已经在表里的行的数据项填充它。 通常,ambuild函数会调用IndexBuildHeapScan()扫描该表以获取现有行并计算需要插入索引的键字。

void
ambuildempty (Relation indexRelation);

创建一个空的索引,并写到给定关系的初始fork(INIT_FORKNUM)中。 这个方法只会为unlogged表调用;在服务器重启动时写入到初始fork的空索引会被复制到主关系。

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

向现有索引插入一个新行。 valuesisnull数组给出需要制作索引的键字值,而heap_tid是要被索引的 TID 。 如果该访问方法支持唯一索引(它的pg_am.amcanunique标志是真), 那么checkUnique指示需要执行这种唯一性检查。 具体情况依赖于该唯一制约是否是可延期的(deferrable)而不同; 详细请参阅 第 54.5 节。 通常访问方法只有在执行唯一性检查时才需要heapRelation参数(它将深入到heap中确认行是否是活的)。

只有当checkUniqueUNIQUE_CHECK_PARTIAL时,该函数的返回值才有意义。 TRUE的返回值意味着新的索引项是唯一的,FALSE意味着不唯一(并且延期的唯一性检查必须被调度)。 其它情况下推荐返回常量FALSE。

一些索引可能并不索引所有的行。如果行不被索引,aminsert应该不做任何事情就直接返回。

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

从索引中删除行。 这是一个"批量删除"的操作,通常都是通过扫描整个索引,检查每条记录,看看它是否需要被删除来实现的。 可以调用传递进来的callback函数,调用风格是: callback(TID, callback_state) returns bool, 其作用是判断某个用其引用的 TID 标识的索引项是否需要删除。 必须返回 NULL 或者是一个 palloc 出来的,包含删除操作执行影响的统计信息的结构。 如果不需要向amvacuumcleanup传递信息,返回 NULL 也是 OK 的。

由于maintenance_work_mem的限制,在删除多行的时候 ambulkdelete可能需要被调用多次,stats 参数是先前在这个索引上的调用结果(在一个VACUUM操作内部第一次调用的话则是 NULL)。 这将允许 AM 在整个操作过程中积累统计信息。 典型的,如果传递的stats不是 null 的话,ambulkdelete将会修改并返回相同的结构。

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

在一个VACUUM操作(一个或多个ambulkdelete调用)之后清理。 虽然不必做任何返回索引状态之外的任何其他事情,但是它通常用于批量清理,比如说回收空的索引页面。 stats是最后的ambulkdelete调用返回的东西或者 NULL(如果因为没有行需要删除而未调用ambulkdelete的话)。 如果结果不是 NULL ,那么它必须是一个 palloc 出来的结构。 它包含的统计信息将用于更新pg_class并且由VACUUM报告(如果给出了VERBOSE)。 如果索引在VACUUM操作的过程中根本没有改变,那么返回 NULL 也是 OK 的,否则必须返回当前状态。

PostgreSQL8.4中,amvacuumcleanup也会在ANALYZE完成时被调用。 这时,stats总是为NULL,并且返回值会被忽略。 通过检查info->analyze_only可以区分出这种情况。 建议访问方法在这样的调用里除了插入后的清理不要做其他事情,并且这只在autovacuum工作进程中。

bool
amcanreturn (Relation indexRelation);

检查索引是否支持index-only扫描,通过为一个索引项以IndexTuple的形式返回被索引的列值。 如果支持返回TRUE,否则返回FALSE。 如果索引AM永远不支持index-only扫描(比如hash,它只存储哈希值而不是原始数据), 可以有充分的理由把pg_am中的amcanreturn字段设置为零。

void
amcostestimate (PlannerInfo *root,
                IndexPath *path,
                double loop_count,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation);

估算一个索引扫描的开销。该函数在下面的第 54.6 节中有详细的讨论。

bytea *
amoptions (ArrayType *reloptions,
           bool validate);

为一个索引分析和验证 reloptions 数组,仅当一个索引存在非空 reloptions 数组时才会被调用。 reloptions是一个text数组,包含name=value格式的项。 该函数应当创建一个bytea值,该值将被拷贝进索引的 relcache 项的rd_options字段。 bytea值的数据内容可以由访问方法定义,不过目前所有的标准访问方法都使用StdRdOptions结构。 当validate为真时,如果任何一个选项不可识别或者含有非法值,该函数都应当报告一个适当的错误消息;当validate为假时,非法项应该被悄悄的忽略。 (当载入已经存储在pg_catalog中的选项时,validate为假,仅在访问方法已经改变了选项规则的时候才可能找到非法项,在此情况下可以忽略废弃的项。) 如果默认行为正是想要的,那么返回 NULL 也 OK 。

索引的目的当然是支持那些包含一个可以索引的WHERE条件的行的扫描,这个条件通常叫修饰词扫描键字。 索引扫描的语义在下面的第 54.3 节里面有更完整的描述。 一个索引访问方法可以支持"plain"索引扫描,"bitmap"索引扫描,或者两者都支持。 必须或可以提供的与扫描有关的函数有:

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的数值。 实际上,重新启动特性用于这样的场景:当一个新的外元组被嵌套循环(nested-loop)连接选中时,需要一个新的键比较值,但是扫描键结构仍然是相同的。

boolean
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

在给出的扫描里抓取下一个行,向给出的方向移动(在索引里向前或者向后)。 如果抓取到了行,则返回 TRUE ,如果没有抓到匹配的行,返回 FALSE 。 在为 TRUE 的时候,该行的 TID 存储在scan结构里。 请注意"成功"只是意味着索引包含一个匹配扫描键字的条目,并不是说该行仍然在堆中存在,或者是能够通过调用者的快照检查(译注:MVCC 快照,用于判断事务边界内的行可视性)。 如果成功,amgettuple必须设置scan->xs_recheck为TRUE或FALSE。 FALSE意味着已经可以确定索引项匹配扫描键字。 TRUE意味着尚不确定,在取到堆元组后必须对堆元组再次检查代表这个扫描键值的条件。

如果索引支持index-only扫描(比如,amcanreturn为它返回TRUE), 那么成功执行后,这个AM也必须检查scan->xs_want_itup,如果为TRUE, 它必须通过存储在scan->xs_itup中的IndexTuple指针以及元组描述符scan->xs_itupdesc为这个索引项返回原始的被索引数据。 (访问方法需要负责维护被这个指针引用的数据。至少在该扫描下一次调用amgettupleamrescanamendscan前,这个数据必须保持完好)

如果访问方法支持"plain"索引扫描,只需要提供amgettuple函数。 如果不是,在它的pg_am行中的amgettuple字段必须被设置成零。

int64
amgetbitmap (IndexScanDesc scan,
             TIDBitmap *tbm);

在指定的扫描中抓取所有元组并把它们加入到调用者提供的TIDBitmap中(换句话说,元组的ID集合加入到某个已存在的bitmap)。 函数返回抓取到的元组数(这可能只是一个近似计数,某些AM实例并不检测重复)。 当插入元组的TID到bitmap,amgetbitmap可以指示对特定的元组TID需要对扫描条件做再检查。 这和amgettuple函数的输出参数xs_recheck类似。 注意:在当前实现中,支持这个特性涉及到支持bitmap自身的有损存储,因此调用者为可再检查的元组再次检查扫描条件和部分索引谓词(如果有的话)。 然而,这可能不会总是正确的。 amgetbitmapamgettuple不能在同一个索引扫描中使用;在使用 amgetbitmap的时候还有其它限制,在第 54.3 节里给出解释。

只有访问方法支持"bitmap"索引扫描时才需要提供amgetbitmap函数。 如果访问方法不支持的话,必须在它的pg_am行里设置amgetbitmap字段为零。

void
amendscan (IndexScanDesc scan);

结束扫描并释放资源。不应该释放scan本身,但访问方法内部使用的任何锁或者销(pin)都应该释放。

void
ammarkpos (IndexScanDesc scan);

标记当前扫描位置。访问方法只需要支持每次扫描里面有一个被记住的扫描位置。

void
amrestrpos (IndexScanDesc scan);

把扫描恢复到最近标记的位置。

通常,任何索引访问方法函数的pg_proc记录都应该显示正确数目的参数, 只是把类型都声明为类型internal(因为大多数参数的类型都是 SQL 不识别的类型,并且不希望用户直接调用该函数)。 返回类型根据具体情况声明为void, internal, or boolean。 唯一的例外是 amoptions,它应当被声明为接受text[]bool并返回bytea。 这样就允许客户端代码执行amoptions以确认选项设置的有效性。