覆盖索引生效的唯一判断标准是explain中extra列为using index;它要求查询所有字段(select、where、order by、group by)均被同一b+树二级索引完全包含,且索引列顺序需满足最左前缀原则,text/blob或select *会直接导致失效。

覆盖索引生效的唯一判断标准是 EXPLAIN 中出现 Using index
不是“建了联合索引就自动覆盖”,也不是“SELECT 的字段在索引里就一定不回表”。真正生效,只看 EXPLAIN 输出的 Extra 列是否为 Using index。其他任何推测都不可靠。
常见误判场景:
-
WHERE条件用了索引,但SELECT多了一个不在索引里的字段 → 回表 - 索引列顺序错误,比如查询
WHERE city = 'shanghai'却建了INDEX(name, city)→ 索引根本没被用上,更谈不上覆盖 - 用了
SELECT *→ 几乎必然回表,除非表只有主键和索引列两列
INDEX(a, b, c) 能覆盖哪些查询?关键看最左前缀 + 字段全包含
联合索引的覆盖能力严格依赖定义顺序和查询结构。例如 INDEX(status, city, name, email):
- 能覆盖:
SELECT name, email FROM t WHERE status = 1 AND city = 'bj'(条件列最左连续,SELECT 列全在索引后缀) - 不能覆盖:
SELECT email FROM t WHERE city = 'bj'(city不是最左,无法走索引) - 能覆盖但低效:
SELECT status, name FROM t WHERE status = 1(name在索引中,但中间跳过city,仍可覆盖,只是city列浪费空间)
注意:id(主键)永远隐含在二级索引叶子节点里,所以 SELECT id, name 比 SELECT name 更容易被覆盖,无需显式把 id 加进索引定义。
为什么加字段进索引反而让查询变慢?索引宽度的真实代价
覆盖索引不是“越多字段越好”。每多一个列,索引页就更臃肿,B+ 树层级可能加深,范围扫描更慢。尤其要注意:
-
VARCHAR(255)实际按最大长度预留空间(utf8mb4 下最多占 1020 字节),极易撑爆单页 -
BIGINT主键会让所有二级索引变大一倍(因叶子存主键值),间接放大覆盖索引体积 - 高频更新的字段放进覆盖索引,会显著拖慢
INSERT/UPDATE性能——每次改数据,都要同步更新索引页
实操建议:优先覆盖高频、低更新、小体积字段;对 TEXT 或长 VARCHAR,宁可回表,也不塞进索引。
回表不是纯坏事,有时它比覆盖索引更优
优化器会权衡成本。当满足以下任一条件时,即使存在覆盖索引,MySQL 也可能主动放弃它,选择扫聚簇索引(即不走覆盖索引):
- 查询返回大量行(比如 > 20% 表数据),顺序读聚簇索引页比随机回表更快
- 覆盖索引太宽,导致单页存储记录数锐减,树高增加,遍历开销反超回表
-
read_rnd_buffer_size足够大,启用MRR(Multi-Range Read)后,回表的随机 I/O 被重排为顺序 I/O,成本大幅下降
这意味着:看到 Using index 是好事,但没看到也不代表索引设计失败——得结合 rows、key_len 和实际执行时间一起看。真正容易被忽略的,是索引宽度与查询选择率之间的隐性博弈。











