0Pricing
SQL Interview Prep · 课时

顺序扫描、索引扫描与仅索引扫描

了解规划器为何选择每种扫描方式,以及这对查询意味着什么。

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

读取表的三种方式

当规划器需要从表中获取行时,会选择三种访问方法之一,面试官希望您能说出这三种方法的名称:

  • 顺序扫描,从头到尾读取表中的每一行。
  • 索引扫描,遍历索引找到匹配的行,然后从表中读取每一行。
  • 仅索引扫描,完全通过索引返回结果,完全不访问表。

理解规划器为何选择每种方法,是本课的核心,也是资深岗位面试必问的问题。

顺序扫描的工作方式

顺序扫描会依次读取表的页面,并对每一行应用过滤条件。它不会使用索引。

这听起来似乎不理想,但它往往是正确的选择。顺序读取对磁盘来说速度很快(不需要随机跳转),因此当查询返回表中很大一部分数据时,读取全部数据比通过索引跳转数百万次更快。

示例:扫描orders,保留满足amount > 100的行。如果大多数订单金额都超过 100,顺序扫描就是正确选择。

EXPLAIN SELECT * FROM orders WHERE amount > 100;

Seq Scan on orders  (cost=0.00..18334.00 rows=900000 width=64)
  Filter: (amount > 100)

索引扫描的工作方式

索引扫描使用 B 树直接跳转到匹配的键,然后从表堆中读取对应的行。

当过滤条件具有高选择性、只返回表中少量数据时,索引扫描最有优势。通过索引查找 5 行,显然胜过读取 1000 万行。

执行计划会列出所使用的索引名称。每个匹配项都需要进行一次索引查找和一次堆读取(随机读取),因此当返回的行过多时,索引扫描就会失去优势。

EXPLAIN SELECT * FROM orders WHERE customer_id = 42;

Index Scan using idx_orders_customer on orders
  (cost=0.42..38.50 rows=12 width=64)
  Index Cond: (customer_id = 42)

选择性决定方式

驱动这一切的核心概念是选择性:谓词保留的行占全部行的比例。

  • 高选择性(匹配行很少,例如唯一标识符)有利于使用索引扫描。
  • 低选择性(匹配行很多,例如status IS NOT NULL)有利于使用顺序扫描。

一个常见的经验法则是:当查询返回表中大约 5% 到 10% 以上的数据时,规划器通常更倾向于顺序扫描,因为索引产生的随机堆读取会比按顺序读取全部数据更昂贵。

仅索引扫描

仅索引扫描是三种方法中最快的。如果查询所需的每一列都已经包含在索引中,引擎就完全不会访问表堆。

示例查询只选择customer_id并按该列进行过滤,而索引也建立在customer_id上。所有所需数据都位于索引中,因此 Postgres 会报告“仅索引扫描”。

这避免了会拖慢普通索引扫描的随机堆读取,在宽表上优势尤其明显。

EXPLAIN SELECT customer_id FROM orders WHERE customer_id = 42;

Index Only Scan using idx_orders_customer on orders
  (cost=0.42..8.44 rows=12 width=4)
  Index Cond: (customer_id = 42)

可见性映射的注意事项

面试官很喜欢考察这个细节。仅索引扫描仍然必须确认每一行对于您的事务是否可见(MVCC),而索引本身并不存储可见性信息。

Postgres 使用可见性映射:如果某个页面被标记为全部可见,就会跳过表堆;否则仍然必须读取表堆中的行。执行计划会显示Heap Fetches: N。

这就是为什么刚刚更新过的表可能会出现大量堆读取,并导致仅索引扫描变慢,直到VACUUM更新可见性映射。

Index Only Scan using idx_orders_customer on orders
  (actual time=0.01..0.03 rows=12 loops=1)
  Heap Fetches: 0

位图扫描:折中方案

还有第四种经常出现的方法:位图堆扫描。当谓词匹配的行数多于普通索引扫描适合处理的数量、但又少于整表扫描需要处理的数量时,规划器就会选择它。

它首先从索引中构建匹配行位置的位图(位图索引扫描),然后按物理顺序读取表堆页面,而不是随机读取。按顺序读取的成本远低于普通索引扫描产生的分散读取。

Bitmap Heap Scan on orders  (cost=12.0..520.0 rows=8000)
  Recheck Cond: (status = 'pending')
  ->  Bitmap Index Scan on idx_orders_status
        (cost=0..12 rows=8000)
        Index Cond: (status = 'pending')

规划器为何忽略您的索引

一个经典的面试问题是:我添加了索引,但执行计划仍然进行顺序扫描,为什么?常见原因包括:

  • 谓词选择性不高,扫描确实更便宜。
  • 函数包裹了列:WHERE lower(email) = ...无法使用建立在电子邮件列上的普通索引。
  • 类型不匹配会强制进行隐式类型转换,从而使索引失效。
  • 统计信息已经过时,请运行ANALYZE。
  • 表非常小,扫描几个页面比承担索引开销更划算。

诊断实例

假设orders的创建时间列上有一个索引,但此查询仍然执行顺序扫描:

问题在于DATE(created_at)。将列包装在函数中意味着无法使用原始创建时间列上的索引。解决方法是将其重写为不包装该列的范围谓词,或者在DATE(created_at)上建立表达式索引。

-- Slow: function on the indexed column
WHERE DATE(created_at) = '2026-01-01'

-- Fast: bare column, range uses the index
WHERE created_at >= '2026-01-01'
  AND created_at <  '2026-01-02'

方法比较

请在面试前记住以下比较:

  • 顺序扫描,适合返回较大比例的行;使用顺序 I/O。
  • 索引扫描,适合高选择性的查找;遍历索引并随机读取表堆。
  • 位图堆扫描,适合中等数量的匹配结果;先通过索引生成位图,再按顺序读取表堆。
  • 仅索引扫描,当索引覆盖所有所需列且页面全部可见时速度最快。

规划器会根据估算的成本进行选择,而成本主要由选择性和统计信息决定。

强制测试(以及为何不应在生产环境中这样做)

为了在开发环境中验证某个观点,您可以暂时引导规划器:SET enable_seqscan = off;会强制它优先使用索引,以便您比较执行计划。

这是一种诊断技巧,绝不是生产环境的修复方案。在面试中请说明,真正的解决办法是改进统计信息、建立合适的索引,或重写谓词,而不是全局禁用规划器功能。

SET enable_seqscan = off;
EXPLAIN ANALYZE SELECT * FROM orders WHERE amount > 100;
SET enable_seqscan = on;

快速检查

一个查询只选择email并按email进行过滤,同时存在一个建立在电子邮件列上的 B 树索引。执行计划显示Index Only Scan。为什么这比普通索引扫描更快?

总结

访问方法的关键要点:

  • 低选择性的查询中,顺序扫描更有优势;高选择性的查询中,索引扫描更有优势。
  • 当索引覆盖所有所需列时,仅索引扫描可以避免访问表堆;请留意Heap Fetches和可见性映射。
  • 位图堆扫描通过按物理顺序读取表堆页面,在两者之间提供了折中方案。
  • 规划器根据选择性和统计信息做出决定;列上的函数、类型不匹配和过时的统计信息,都是索引被忽略的原因。

常见问题解答

「顺序扫描、索引扫描与仅索引扫描」课时是免费的吗?

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

「顺序扫描、索引扫描与仅索引扫描」这节课中我会学到什么?

了解规划器为何选择每种扫描方式,以及这对查询意味着什么。 你通过在浏览器中直接运行的动手代码来练习 SQL Interview Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

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

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

「顺序扫描、索引扫描与仅索引扫描」课时需要多长时间?

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

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

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

此课程中的所有课时

  1. 阅读 EXPLAIN 执行计划
  2. 顺序扫描、索引扫描与仅索引扫描
  3. 连接算法:嵌套循环、哈希与归并
  4. 发现并修复慢查询
← 返回 SQL Interview Prep