61.3. 索引扫描

在一个索引扫描中,索引访问方法负责提供它拿到的匹配扫描键的所有元组的TID。访问方法会涉及从索引的父表中实际取得那些元组,也不会涉及判断它们是否通过了扫描的可见性测试或者是其它条件。

一个扫描键是一个WHERE子句的内部表示,WHERE子句的形式是index_key operator constant,其中索引键字索引中的一个列,而操作符是和该索引列相关联的操作符族的一个成员。一个索引扫描拥有零个或者多个扫描键,它们是隐式 AND 关系 — 返回的元组被认为满足所有列出的条件。

对于一个特定查询,访问方法可能报告索引是有损的或者要求重新检查。这就暗示着该索引扫 描会返回所有通过扫描键的项,外加上一些可能没通过扫描键的项。核心系统的索引扫描机制然后就会再次在堆元组上应用索引条件来验证它是否真地应该被选择。如果没有指定重新检查选项,索引扫描必须返回准确的匹配项集合。

请注意,确保找到所有(只有)通过所有给定扫描键的条目的工作完全由访问方法负责。还有,核心系统将只是简单地 放过所有匹配扫描键和操作符族的WHERE子句,而不会做任何语义分析来判断它们是否冗余或者矛盾。例如,给定WHERE x > 4 AND x > 14(其中x是一个 B-树 索引列,它被留给 B-树 amrescan函数来发现第一个扫描键是冗余并且可以被丢弃。amrescan期间需要的预处理的范围将取决于索引访问方法需要什么来把扫描键缩减为一种正规化的形式。

一些访问方法按照一个良定义的顺序来返回索引项,其他的则不会。实际上一个访问方法可以有两种不同的方式支持排序输出:

amgettuple函数有一个direction参数,它可以是 ForwardScanDirection(正常情况)或者BackwardScanDirection。如果amrescan之后的第一次调用指定了BackwardScanDirection,那么匹配条件的索引项集合是从后向前扫描的,而 不是通常的从前向后扫描,因此amgettuple必须返回索引中最后一个匹配元组,而不是通常情况下的第一个(这只对设置了amcanorder为真访问方法发生)。在第一次调用后,amgettuple必须被准备好从最近被返回项的位置按照任何一种方向推进扫描(但是如果amcanbackward为假,所有后续调用将使用第一次相同的方向)。

支持排序扫描的访问方法必须支持在扫描里标记一个位置并且随后返回到这个标记过的位置。同一个位置可能会被重复多次还原。但是,每个扫描中只有一个位置需要被记住;一个新的ammarkpos调用将重写之前标记的位置。一个不支持排序扫描的访问方法无需在IndexAmRoutine中提供ammarkposamrestrpos函数,把这些指针设置为 NULL 即可。

扫描位置和标记位置(如果存在))都必须在面对索引中的并发插入和删除时保持一致性。如果一个新插入的项并未被一个扫描返回(如果该扫描开始的时候该项已经存在,该扫描将已经找到该项),或者说扫描通过重新扫描或者反向扫描返回这样一个项(即使它第一次没有返回这样一个项),这些情况都是可以接受的。类似的还有,一个并发的删除可能或不可能被反映在一个扫描的结果中。重要的是,插入或者删除不会导致扫描错过或者多次返回本身不是被插入或者删除的项。

如果索引存储原始被索引的数据值(并且不是它们的某种有损表示),它可用来支持只用索引的扫描,着这种扫描中索引返回的就是实际的数据而不只是堆元组的 TID。这只有在可见性映射显示该 TID 位于一个全部可见的页面时才能避免 I/O;否则必须访问堆元组来检查 MVCC 可见性。但是这就不用访问方法操心了。

除了使用amgettuple,一个索引扫描可以通过amgetbitmap在一次调用中取得所有元组来完成。这样做可能会比amgettuple有显著的效率提升,因为它可以避免在访问方法内的加锁/解锁循环。原则上amgetbitmap应该和重复调用amgettuple的效果相同, 不过我们强加了一些限制来简化这件事。首先,amgetbitmap一次返回 所有元组并且标记并且不支持标记或恢复扫描位置。第二,在一个位图中返回的元组没有任何指定的顺序,这也是为什么amgetbitmap没有一个direction参数的原因(排序操作符也将永远不会提供给这种扫描)。还有,对于使用amgetbitmap的只用索引扫描没有规定,因为没有办法返回索引元组的内容。最后,如第 61.4 节中所说的,amgetbitmap不保证被返回元组上的任何锁。

注意如果访问方法的内部实现不适合一个 API 或其他 API,允许一个访问方法只实现amgetbitmap而不实现amgettuple,或者反过来。