覆盖索引为什么能避免回表
MySQL 的二级索引(非聚簇索引)只存储索引列 + 主键值,不存整行数据。当
SELECT的所有字段都包含在某个索引中时,优化器可以直接从该索引页拿到全部所需数据,无需再用主键去聚簇索引里查一次完整记录——这就是“回表”。覆盖索引本质是让查询“止步于二级索引”,跳过回表开销。
常见误判:以为只要
WHERE条件用了索引就算覆盖。其实必须
SELECT列、
WHERE列、
ORDER BY列、
GROUP BY列全部被同一索引包含才算真正覆盖。
如何确认一个查询是否走覆盖索引
执行
EXPLAIN,重点看两列:
type为
ref/
range/
const等合理类型(不是
ALL)
Extra字段不含
Using filesort或
Using temporary,且明确出现
Using index(注意不是
Using index condition)
示例:
EXPLAIN SELECT user_id, status FROM orders WHERE shop_id = 123;
若
orders表有联合索引
(shop_id, user_id, status),则该语句会显示
Extra: Using index;但如果只查
SELECT *或多了
created_at这种未包含字段,就会变成
Using where; Using index(回表发生)。
设计覆盖索引的实操要点
覆盖索引不是越多越好,要结合高频查询模式权衡:
把WHERE最左列放在索引最前(符合最左前缀原则) 把
SELECT中所有需返回的列追加在右侧(顺序无关,但影响索引体积) 避免把大字段(如
TEXT、长
VARCHAR)放进索引,否则索引页膨胀,反而拖慢扫描速度 如果查询常带
ORDER BY a,b,且
a已在
WHERE条件中,则把
a,b放进索引可避免排序(
Using index同时覆盖查找+排序)
反例:为
SELECT name FROM users WHERE age > 25建索引
(age, name, email)——
覆盖索引失效的典型场景
即使建了看似完整的索引,以下情况仍会强制回表:
SELECT中用了函数或表达式:
SELECT UPPER(name) FROM t WHERE id = 1→ 即使有
(id, name)索引,
UPPER()导致无法直接取索引值 隐式类型转换:
WHERE phone = 13800138000(phone 是
VARCHAR),触发全索引扫描+回表 使用
!=、
NOT IN、
IS NULL等无法利用索引有序性的条件,优化器可能放弃覆盖路径 索引列上用了
LIKE '%xxx',导致无法走索引查找,自然也无法覆盖
真正起作用的永远是执行计划里的
Using index,而不是“我以为它应该能覆盖”。每次加索引后,务必用真实查询
EXPLAIN验证。
