48.2. 索引访问方法函数

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

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

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

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

向现有索引插入一个新元组。valuesisnull 数组给出需要制作索引的键字值, 而 heap_tid 是要被索引的 TID。 如果该访问方法支持唯一索引(它的 pg_am.amcanunique 标志是真), 那么 check_uniqueness 可以是真,在这种情况下,该索引访问方法必须校验表中不存在冲突的行; 通常这是该索引访问方法会需要 heapRelation 参数的唯一的情况。 参阅 Section 48.5 获取细节。 如果插入了索引记录,则返回 TRUE,否则返回 FALSE。 (FALSE 结果并不表明发生了错误,只是用于类似一种索引访问方法(AM)拒绝给 NULL 建索引或者类似的场合。)

IndexBulkDeleteResult *
ambulkdelete (Relation indexRelation,
              IndexBulkDeleteCallback callback,
              void *callback_state);

从索引中删除元组。这是一个"大批删除"的操作, 通常都是通过扫描整个索引,检查每条记录,看看它是否需要被删除来实现的。 我们可以调用传递进来的 callback 函数,调用风格是: callback(TID, callback_state) returns bool, 其作用是判断某个用其引用的 TID 标识的索引条目是否需要删除。 必须返回 NULL 或者是一个 palloc 出来的,包含删除操作之效果的统计的结构。

IndexBulkDeleteResult *
amvacuumcleanup (Relation indexRelation,
                 IndexVacuumCleanupInfo *info,
                 IndexBulkDeleteResult *stats);

在一个 VACUUM 操作(一个或多个 ambulkdelete 调用)之后清理。 一个索引访问方法不一定非要提供这个函数(如果提供了,那么在 pg_am 里的记录必须为零)。 如果提供了这个函数,它通常用于批量清理,比如说回收空的索引页面。 info 提供了一些额外的参数,比如用于统计报告的消息级别, 而 stats 是最后的 ambulkdelete 调用返回的东西。 amvacuumcleanup 可以在返回这个结构之前替换或者修改它。 如果结果不是 NULL,那么它必须是一个 palloc 出来的结构。 如果给出了 VERBOSE,那么其包含的统计信息将被 VACUUM 报告。

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

IndexScanDesc
ambeginscan (Relation indexRelation,
             int nkeys,
             ScanKey key);

开始一个新的扫描。key 数组(长度是 nkeys)为该索引扫描描述索引键字(可能是多个)。 结果必须是一个 palloc 出来的结构。由于实现的原因,索引访问方法必须通过调用 RelationGetIndexScan() 来创建这个结构。 在大多数情况下,ambeginscan 本身除了调用上面这个函数之外几乎不干别的事情; 索引扫描启动时的有趣部分在 amrescan 里。

boolean
amgettuple (IndexScanDesc scan,
            ScanDirection direction);

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

boolean
amgetmulti (IndexScanDesc scan,
            ItemPointer tids,
            int32 max_tids,
            int32 *returned_tids);

在给出的扫描里抓取多个元组。如果扫描需要继续,则返回 TRUE,如果没有剩下的匹配元组,返回 FALSE。 tids 指向一个调用着提供的 max_tidsItemPointerData 记录的数组, 用于填充匹配元组的 TID。*returned_tids 设置为实际返回的 TID 的数目。 这个数目可以小于 max_tids,或者甚至是零,即使返回值是 TRUE 也如此。 (这样的设计就允许访问方法可以选择对其扫描的最高效的停止点,比如,在索引页的边界上。) amgetmultiamgettuple 不能在同义词索引扫描中使用; 在使用 amgetmulti 的时候还有其它限制,在 Section 48.3 里给出解释。

void
amrescan (IndexScanDesc scan,
          ScanKey key);

重启开始给出的扫描,可能使用的是一个新的扫描键字(要想继续使用原来的键字, 给 key 传递一个 NULL)。请注意,我们不可能改变键字的个数。 实际上这个重新开始的特性是在一个嵌套循环连接选取了一个新的外层元组,因此需要一个新的键字比较值, 但扫描键字的结构仍然相同的时候使用的。 这个函数也被 RelationGetIndexScan() 调用, 因此这个函数既用于索引扫描的初始化设置,也用于重复扫描。

void
amendscan (IndexScanDesc scan);

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

void
ammarkpos (IndexScanDesc scan);

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

void
amrestrpos (IndexScanDesc scan);

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

void
amcostestimate (PlannerInfo *root,
                IndexOptInfo *index,
                List *indexQuals,
                Cost *indexStartupCost,
                Cost *indexTotalCost,
                Selectivity *indexSelectivity,
                double *indexCorrelation);

估计一个索引扫描的开销。这个函数在下面的 Section 48.6 里详细描述。

通常,任何索引访问方法函数的 pg_proc 记录都应该显示正确数目的参数, 只是把类型都声明为类型 internal (因为大多数参数的类型都是 SQL 不识别的类型, 并且我们不希望用户直接调用该函数)。返回类型根据具体情况声明为 voidinternal,或者 boolean