覆盖索引能避免回表,是因为其查询所需所有字段(SELECT、WHERE、ORDER BY等)均被同一二级索引完全包含,而InnoDB二级索引叶子节点天然存储索引列值和主键值,无需再通过主键去聚簇索引中随机查找整行数据;判断依据是EXPLAIN中Extra显示“Using index”。

覆盖索引能彻底绕过随机I/O,是因为它让 MySQL 完全不需要访问数据页(即聚簇索引的叶子节点),所有字段都从二级索引的叶子节点里顺序读出来——而二级索引的叶子节点在物理上是按索引键顺序连续存储的。
覆盖索引为什么不用回表?
回表的本质是:二级索引只存了「索引列 + 主键」,查完索引拿到主键后,还得拿这个主键去聚簇索引里随机跳转找整行。这就是一次额外的、无法预测位置的磁盘寻址(随机I/O)。
覆盖索引打破了这个链条:只要 SELECT 的字段和 WHERE / ORDER BY / GROUP BY 涉及的字段,全部包含在同一个二级索引中,MySQL 就直接从该索引的叶子节点取值,不再碰主键、不查聚簇索引。
- 关键判断依据是
EXPLAIN输出中的Extra: Using index,不是Using index condition - 注意:即使索引定义里没显式包含主键,InnoDB 的二级索引叶子节点**自动隐含主键值**,所以
SELECT id, col1, col2 FROM t WHERE col1 = ?只要(col1, col2)是联合索引,就天然覆盖id - 但
SELECT *几乎永远无法覆盖,除非你把所有字段都加进索引——这通常不可取
为什么二级索引扫描是顺序I/O?
B+ 树的二级索引叶子节点,在磁盘上是按索引键值顺序物理排列的(至少在每个数据页内连续,且页之间有双向链表)。当执行全索引扫描或范围扫描时,MySQL 会按页链表顺序读取,触发操作系统预读(read-ahead),一次加载多个相邻页到 buffer pool。
- 机械盘下,顺序读的 IOPS 是随机读的 5–10 倍;NVMe 下差距小些,但延迟仍低 10~50 倍
-
type: index(全索引扫描)或type: range(范围扫描)+Extra: Using index,才真正享受顺序I/O红利 - 如果
WHERE条件导致索引失效(比如LIKE '%abc'),优化器可能退化为全表扫描,那就又回到离散读数据页的老路
哪些写法会悄悄破坏覆盖效果?
看似只查索引字段,但某些操作会让 MySQL 被迫“出索引”:
-
SELECT DISTINCT col1 FROM t WHERE col2 = ?:若col2是索引前导列,但col1不在索引中,DISTINCT 需去重,可能触发临时表或文件排序,进而需要回表 -
ORDER BY col3且col3不在索引中:即使SELECT字段都覆盖了,排序无法利用索引,MySQL 可能放弃覆盖索引改走其他路径 - 使用函数或表达式:
SELECT UPPER(name) FROM t WHERE name = 'a',即使name在索引中,UPPER()也会让优化器无法安全确认是否能覆盖(取决于是否用函数索引) - 隐式类型转换:
WHERE phone = 13800138000(phone 是 VARCHAR),可能导致索引失效,连带覆盖失效
最易被忽略的一点:覆盖索引对写入有真实代价——每条 INSERT/UPDATE/DELETE 都要同步更新索引数据,索引越宽、越多,写放大越严重。别为了几个查询强行把 10 个字段塞进一个索引,优先保主键和高频过滤字段,再按需扩展覆盖字段。


















