PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

63.3. 索引扫描 #

在一次索引扫描中,索引访问方法负责吐出它被告知的、匹配 扫描键的所有元组的 TID。 访问方法不参与实际从索引的父表中取回这些 元组,也不参与判断它们是否通过扫描的可见性测试或其他 条件。

扫描键是形如 index_key operator constant 的 WHERE 子句的内部表示,其中索引键是索引的 列之一,操作符是与该索引列关联的操作符族的成员之一。 一次索引扫描拥有零个或多个扫描键,它们被隐式地 AND 起来 — 期望返回的元组满足所有指出的条件。

对特定查询,访问方法可以报告该索引是 有损的或需要重新检查。 这意味着索引扫描将返回所有通过扫描键的条目,外加可能还有不通过的 额外条目。 核心系统的索引扫描机制随后会再次把索引条件应用到堆元组上, 验证它是否真的应当被选出。 如果未指定 recheck 选项,索引扫描必须精确返回匹配条目的 集合。

注意,确保正确找出所有且仅找出通过所有给定扫描键的条目完全是 访问方法的责任。 另外,核心系统会直接交出所有匹配索引键和操作符族的 WHERE 子句,而不做任何判断它们是冗余还是矛盾的语义分析。 例如,给定 WHERE x > 4 AND x > 14(其中 x 是 b-tree 索引列), 意识到第一个扫描键是冗余的、可以丢弃,是 b-tree amrescan 函数的职责。 amrescan 期间所需的预处理程度取决于索引 访问方法需要把扫描键化简为 “规范化”形式的程度。

一些访问方法以明确定义的顺序返回索引条目,另一些则不然。 实际上访问方法支持排序输出有两种不同的方式:

amgettuple 函数有一个 direction 参数, 它可以是 ForwardScanDirection(正常情况) 或 BackwardScanDirection。 如果 amrescan 之后的第一次调用指定了 BackwardScanDirection,那么匹配索引条目的 集合要按从后向前的方向扫描,而非正常的从前向后方向,因此 amgettuple 必须返回索引中最后匹配的元组, 而不是像通常那样返回第一个。 (这只对把 amcanorder 设为 true 的访问方法 发生。) 第一次调用之后,amgettuple 必须准备好从最近 返回的条目起向任一方向推进扫描。 (但如果 amcanbackward 为 false,所有后续 调用都会与第一次方向相同。)

支持有序扫描的访问方法必须支持在一次扫描中 “标记”一个位置并稍后返回到被标记的位置。 同一个位置可以被恢复多次。 不过,每次扫描只需记住一个位置;一次新的 ammarkpos 调用会覆盖之前标记的位置。 不支持有序扫描的访问方法无需在 IndexAmRoutine 中提供 ammarkpos 和 amrestrpos 函数;把这些指针设为 NULL 即可。

面对索引中的并发插入或删除,扫描位置和标记位置(如果有)都必须 得到一致的维护。 如果一个新插入的条目没有被一次扫描返回(即使它在扫描开始时存在 就会被找到),或者扫描在重新扫描或回退时返回了这样一个第一次 未返回的条目,都是可以的。 类似地,一次并发删除可能反映也可能不反映在扫描结果中。 重要的是,插入或删除不得导致扫描漏掉或多重返回那些本身没有被 插入或删除的条目。

如果索引存储原始的被索引数据值(而非它们的有损表示), 支持 仅索引扫描会很有 用,其中索引返回实际数据而不只是堆元组的 TID。 只有当可见性映射显示 TID 位于一个全可见页面时,这才会避免 I/O;否则无论如何都必须访问堆元组来检查 MVCC 可见性。 但这不是访问方法需要关心的事。

也可以不使用 amgettuple,而用 amgetbitmap 在一次调用中取回所有元组来完成 索引扫描。 这可能比 amgettuple 明显更高效,因为它允许 避免访问方法内部的加锁/解锁循环。 原则上 amgetbitmap 应当具有与重复 amgettuple 调用相同的效果,但为了简化问题, 我们施加了几项限制。 首先,amgetbitmap 一次返回所有元组,标记 或恢复扫描位置不被支持。 其次,元组在一个没有任何特定排序的位图中返回,这也是 amgetbitmap 不接受 direction 参数的原因。 (这样的扫描也不会提供排序操作符。) 此外, amgetbitmap 没有对仅索引扫描的支持规定, 因为没有办法返回索引元组的内容。 最后,amgetbitmap 不保证对返回的元组有任何加锁,其影响在 Section 63.4 中说明。

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