← 返回题目列表

覆盖索引为什么能减少 B+ 树回表?它和普通二级索引有什么区别?

高频 中等 第 1 / 25 题 更新于 2026/08/03
B+树覆盖索引回表二级索引

简化版

覆盖索引指查询需要的列都能从某个索引里拿到,不必再根据二级索引叶子中的主键去聚簇索引查整行。它减少了一次 B+ 树查找和随机 I/O,尤其适合读多、返回列少的查询。

详细版

以 InnoDB 为例,二级索引叶子节点通常保存「二级索引 key + 主键值」。如果查询列不在二级索引中,流程是:

  1. 先走二级索引找到主键;
  2. 再用主键回到聚簇索引查整行;
  3. 从整行里取需要的列。

如果索引已经包含查询所需列,例如 (user_id, status, created_at) 能满足:

select status, created_at from t where user_id = ?

那么数据库扫描二级索引叶子就能返回结果,不需要回表。这就是覆盖索引。它的代价是索引变宽,占更多磁盘和内存,写入更新成本更高。

完整版教学

一、为什么会有回表

在聚簇索引组织的存储引擎里,表数据本身按主键 B+ 树组织,叶子页保存完整行。二级索引也是 B+ 树,但它的叶子页通常不保存整行,而是保存二级索引列和主键。这样可以避免每个二级索引都复制完整行,节省空间。

回表就是二级索引查到主键后,再去主键聚簇索引查一次完整行。它本质上是两次树查找:一次从二级索引树到主键,一次从聚簇索引树到行数据。

记忆钩子:二级索引像通讯录里的「姓名 -> 身份证号」,回表就是拿身份证号再去档案柜取完整档案。

二、覆盖索引怎样避免回表

如果查询需要的列已经全部存在于二级索引叶子里,数据库就不必去聚簇索引拿整行。例如索引 (user_id, status, created_at),查询只需要 statuscreated_at,并且条件是 user_id=?,那么扫描这个索引就够了。

普通二级索引:
secondary B+Tree -> primary key -> clustered B+Tree -> row

覆盖索引:
secondary B+Tree -> needed columns -> return

少一次 B+ 树查找,尤其在结果很多、主键分散时,能明显减少随机 I/O。

三、覆盖索引和联合索引是什么关系

覆盖索引不一定是单独的索引类型,它通常由联合索引实现。一个索引是否覆盖,取决于它是否包含这条 SQL 所需的列。对另一条 SQL 来说,同一个索引可能就不覆盖。

SQL索引 (user_id,status,created_at) 是否覆盖
select status from t where user_id=?覆盖
select created_at,status from t where user_id=?覆盖
select name from t where user_id=?不覆盖
select * from t where user_id=?通常不覆盖

所以覆盖索引是「查询视角」的概念,不是建索引语句里某个特殊关键字。

四、为什么覆盖索引不等于一定更快

覆盖索引减少读路径回表,但会让索引更宽。索引越宽,一个页能放下的索引项越少,B+ 树扇出下降,缓存命中也可能变差。写入、更新、删除时还要维护更多索引内容。

举个数字例子:16KB 页里,如果索引项平均 32B,大约能放 500 条;如果为了覆盖把索引项扩到 128B,只能放约 125 条。叶子页更多,扫描和缓存压力都会变大。

五、如何判断一条查询是否真正覆盖

判断覆盖要看 select 列、where 列、order by 列、group by 列,以及存储引擎二级索引叶子天然包含的主键。只要执行这条查询不需要读取索引之外的列,就可以认为覆盖。

在 MySQL 执行计划里,常见标志是 Using index,表示可以只通过索引返回数据。但执行计划还要结合 typerows、过滤条件等一起看,不能只盯一个标志。

六、覆盖索引的设计取舍

覆盖索引适合高频、读多、返回列少、延迟敏感的查询。不要为了覆盖所有 SQL 把索引建得特别宽,那会让写入成本和存储成本变高。更好的做法是针对核心查询设计少量高价值联合索引。

适合覆盖不适合盲目覆盖
高频列表页查询很少执行的后台查询
只返回少数列select *
回表代价高、结果多写入非常频繁的宽表
排序分页稳定列经常更新

索引设计本质是读写空间三方权衡,不是越多越宽越好。

七、常见误区与追问

  • 误区:覆盖索引是一种新的树结构。 它仍是 B+ 树索引,只是查询列都被索引覆盖。
  • 追问:为什么二级索引要回表? 二级索引叶子通常只保存索引列和主键,不保存完整行。
  • 误区:只要用了索引就没有回表。 用二级索引定位后仍可能需要回聚簇索引取其他列。
  • 追问:覆盖索引有什么代价? 索引变宽,占空间、降扇出、增加写维护成本。
  • 误区:select * 很容易覆盖。 除非索引包含所有列,否则通常无法覆盖。
  • 追问:主键列是否也算被二级索引覆盖? 在 InnoDB 二级索引叶子里通常包含主键值,所以只查二级索引列和主键时经常可以覆盖。

八、加强记忆

覆盖索引的核心是「索引页里就有答案」。普通二级索引常是先查索引再按主键回表;覆盖索引让查询需要的列都在二级索引叶子中,省掉第二次 B+ 树查找。它提升读性能,但会让索引更宽、更贵。面试里要把覆盖索引、联合索引、回表三者串起来讲,答案就很稳。