0Pricing
SQL Interview Prep · 课时

覆盖索引与仅索引扫描

纳入所需列,使查询无需访问表堆。

覆盖索引与仅索引扫描 是 CoddyKit 上的免费 SQL Interview Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 SQL Interview Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 SQL Interview Prep 课程共包含 4 节课。

回顾堆读取

您之前已经了解到,普通 B 树只存储索引列和行指针,因此索引找到匹配项后,引擎仍需跳转到表中读取其他列。这次跳转就是堆读取,也是覆盖索引所要消除的成本。

面试官会询问覆盖索引,以了解您是否理解索引为什么能够在不访问表的情况下完整回答查询。

“覆盖”的含义

当查询在 SELECT、WHERE、ORDER BY 和 GROUP BY 中所需的每一列都存在于索引本身时,该索引就覆盖了这个查询。

满足这一条件后,引擎只读取索引,完全不会访问表。PostgreSQL 将其称为仅索引扫描;SQL Server 和其他数据库则称其为覆盖索引。这样做的好处是减少页读取并加快查询。

示例:一个被覆盖的查询

假设某个查询只需要 customer_id 和 order_date。一个恰好包含这两列的复合索引已经包含查询所需的全部内容,因此可以只从索引中完成查询。

CREATE INDEX idx_orders_cust_date
  ON orders (customer_id, order_date);

-- Covered: both selected columns are in the index
SELECT customer_id, order_date
FROM orders
WHERE customer_id = 42;

多出一列就无法覆盖

增加索引中不存在的列后,覆盖就会失效,引擎必须读取堆来获取该列。

这里的 total 不在索引中,因此即使 customer_id 驱动了查找,每个匹配行仍会触发一次堆读取来读取 total。

-- NOT covered: total is not in the index, forces heap fetches
SELECT customer_id, order_date, total
FROM orders
WHERE customer_id = 42;

INCLUDE 子句

您可以将 total 添加为第四个键列,但如果从不按它筛选或排序,就会浪费树的排序空间。更合适的工具是 INCLUDE(PostgreSQL 和 SQL Server 支持):它只将额外列存储在索引的叶节点中,作为载荷数据,而不是排序键的一部分。

这样,查询无需扩充索引的可搜索部分就能得到覆盖。

CREATE INDEX idx_orders_cust_date_inc
  ON orders (customer_id, order_date)
  INCLUDE (total);

-- Now covered: total is carried in the leaf
SELECT customer_id, order_date, total
FROM orders
WHERE customer_id = 42;

键列与包含列

一个能给面试官留下深刻印象的精确区分:

  • 键列决定排序顺序,可用于查找和范围扫描,并且遵循最左前缀规则。
  • 包含列只作为额外数据存储在叶节点中;它们不能用于搜索,但可以让索引覆盖更多查询。

经验法则:用于筛选或排序的列放入键列;只用于返回的列放入 INCLUDE。

MySQL/InnoDB:聚簇索引的特殊之处

体现您对不同数据库方言的了解。InnoDB(MySQL)表按主键聚簇:二级索引会隐式携带主键列。因此,只要查询选择的列仅包括索引列和主键列,二级索引就会自动覆盖该查询,无需 INCLUDE 子句(MySQL 没有 INCLUDE)。

覆盖索引这一概念是通用的;语法和附带列则因数据库引擎而异。

验证仅索引扫描

使用 EXPLAIN 证明覆盖效果。在 PostgreSQL 中,执行计划节点会显示仅索引扫描,而不是 Index Scan。请在 EXPLAIN (ANALYZE) 中查找 Heap Fetches: 0;这是未访问表的确凿标志。

如果您预期执行仅索引扫描,却看到带有堆读取的 Index Scan,说明索引中缺少某个被选取的列。

EXPLAIN (ANALYZE)
SELECT customer_id, order_date, total
FROM orders
WHERE customer_id = 42;
-- Look for: Index Only Scan ... Heap Fetches: 0

PostgreSQL 可见性映射的注意事项

有一个值得额外说明的 PostgreSQL 细节:如果某个页面未在可见性映射中标记为对所有事务可见,仅索引扫描仍可能访问堆。在大量更新之后,请运行 VACUUM 以更新可见性映射;否则 Heap Fetches 会增加,“仅索引”的收益也会缩小。

-- Keeps the visibility map fresh so index-only scans stay heap-free
VACUUM ANALYZE orders;

何时不应构建宽覆盖索引(NOT)

覆盖索引并不是没有代价的。将许多列塞入 INCLUDE 会使索引变得庞大,占用缓存并减慢写入速度(每次相关写入都会更新索引)。面试中应明确说明以下权衡:

  • 非常适合访问频繁、列数少且读取频率高的查询。
  • 不适合把索引当成“以防万一”存放所有列的地方。

只覆盖真正重要的查询,而不是覆盖整行。

面试中如何表述

一个简洁的总结:

“覆盖索引包含查询所涉及的每一列,因此引擎可以只从索引中回答查询,执行仅索引扫描并跳过堆读取。我将用于搜索的列放入键列,将仅用于返回的列放入 INCLUDE,使用 EXPLAIN ANALYZE 验证堆读取次数为零,并保持索引精简以保护写入速度。”

快速检查

思考哪些列应放在什么位置,以及如何判断查询是否得到覆盖。

回顾:覆盖索引

要点总结:

  • 当索引包含查询所需的每一列时,就覆盖了该查询,可以执行仅索引扫描而无需堆读取。
  • 键列驱动查找并遵循最左前缀规则;INCLUDE 列是仅存储在叶节点中的载荷数据,用于实现覆盖。
  • InnoDB 二级索引会隐式包含主键。
  • 使用 EXPLAIN (ANALYZE) 进行验证,并关注 Heap Fetches;在 PostgreSQL 中保持 VACUUM 状态最新。
  • 保持覆盖索引精简,以保护写入性能。

下一节:反面情况——索引实际上会带来损害的情形。

常见问题解答

「覆盖索引与仅索引扫描」课时是免费的吗?

是的 — 「覆盖索引与仅索引扫描」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 SQL Interview Prep 课程的其余内容,请升级到 CoddyKit PRO。 SQL Interview Prep 课程共包含 4 节课。

「覆盖索引与仅索引扫描」这节课中我会学到什么?

纳入所需列,使查询无需访问表堆。 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 SQL Interview Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 SQL Interview Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「覆盖索引与仅索引扫描」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 SQL Interview Prep 课中编写并运行代码吗?

能。每节 SQL Interview Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. B 树索引及其作用
  2. 复合索引的列顺序
  3. 覆盖索引与仅索引扫描
  4. 索引何时有害:写入与选择性
← 返回 SQL Interview Prep