48.3. 索引扫描

在一个索引扫描里,索引访问方法负责把它拿到的那些据说匹配扫描键字的所有元组之 TID 的回流。访问方法会卷入从索引的父表中实际抓取这些元组的动作中, 也不会判断他们是否通过了扫描的时间条件测试或者是其它条件。

一个扫描键字是形如 index_key operator constantWHERE 子句的内部表现形式,这里的索引键字是索引中的一个字段, 而操作符是和该索引字段相关联的操作符表的一个成员。一个索引扫描拥有零个或者多个扫描键字, 他们是隐含着 AND 的关系 — 返回的元组被认为是满所所有列出的条件的元组。

操作符表可能会指出改索引对于某些特定的操作符是有损耗的; 这就暗示着该索引扫描会返回所有通过扫描键字的条目,加上一些可能没通过扫描键字的条目。 核心系统的索引扫描机制然后就会再次在堆元组上使用该操作符,以校验这些条目是否真正应该选取。 对于无损耗的操作符,索引扫描必须返回全部匹配的条目,不需要重复校验。

请注意,确保找到所有条目以及确保所有条目都通过给出的扫描键字的条件完全是访问方法的责任。 还有,核心系统将只是简单的吧所有匹配扫描键字和操作符表的 WHERE 子句传递过来, 而不会做任何语义分析,以判断他们是否冗余或者是否相互矛盾。 举例来说,给出 WHERE x > 4 AND x > 14,这里的 x 是一个 b-tree 索引字段,那么把第一个扫描键字识别成冗余的和可抛弃的工作是 b-tree amrescan 函数的事。amrescan 过程中所需要的预处理的范围将由索引访问方法把扫描键字缩减为一个"正常"形式的具体需要而定。

amgettuple 函数有一个 direction 参数, 它可以是 ForwardScanDirection(正常情况)或者 BackwardScanDirection。 如果 amrescan 之后的第一次调用声明 BackwardScanDirection, 那么匹配条件的索引记录集是从后向前扫描的,而不是通常的从前向后扫描, 因此 amgettuple 必须返回索引中最后的匹配元组,而不是通常情况下的第一条。 (这些事情只会是那些设置了 pg_am.amorderstrategy 为非零的, 号称自己支持排序扫描的访问方法上会发生。)在第一次调用之后,amgettuple 必须准备从最近返回的条目的位置开始,在两个方向上进行扫描步进。

访问方法必须支持在扫描里"标记"一个位置并且在后面的操作中返回到标记的位置。 同样的位置可能会被回复好几次。不过,每次扫描只需要记住一个位置; 新的 ammarkpos 调用覆盖前面标记的位置。

扫描位置和标记位置(如果存在)都必须在面对索引中存在并发插入和删除的时候保持一致性。 如果一条并发新插入的记录并未被一次扫描返回(而如果扫描开始的时候该记录存在,则会被返回), 或者说扫描通过重新扫描或者回头扫描返回这样的记录 — 即使它第一次跑的时候没有返回这样的行, 对于系统来说,这些情况都是可以接受的。 类似的还有,一个并发的删除可以反映,也可以不反应一个扫描的结果。 重要的是,插入或者删除不会导致扫描会略过或者重复返回本身不是被插入或者删除的条目。 (对于没有设置 pg_am.amconcurrent 的索引类型, 只要在处理进行扫描的同个后端里的这些插入和删除动作就足够了。 但是如果 amconcurrent 为真,那么就必须处理其它后端进行的插入或者删除。)

除了使用 amgettuple,索引扫描页可以用 amgetmulti, 每次调用抓取多条元组的方式完成。这样做可能会比 amgettuple 有显著的效率提升, 因为它可以避免在访问方法内的加锁/解锁的循环。在原理上,amgetmulti 应该和重复调用 amgettuple 的效果相同,不过我们强制了一些限制来简化事情。 首先,amgetmulti 并不接受 direction 参数, 因此它不支持反向扫描,页不支持内部扫描方向的回向。 访问方法也不需要支持在 amgetmulti 扫描中扫描位置的标记以及恢复。 (这些限制几乎没啥开销,因为在 amgetmulti 扫描里使用这些特性是很困难的: 调整调用着的 TID 缓冲列表是一件很复杂的事情。) 最后,amgetmulti 并不保证在返回的元组上的任何锁定, 这就暗示着 Section 48.4 里面的事情。