回表就是“查两次索引”
当你用
WHERE age = 25查询,而
age上只有普通索引(比如
INDEX idx_age(age)),但 SELECT 的字段包含
name或
city这些没在索引里的列时,MySQL 就必须:先查二级索引拿到匹配的主键
id列表,再拿每个
id去主键索引里逐条找完整行——这第二次查找,就是回表。
本质不是“访问表”,而是“访问聚簇索引”。InnoDB 中,数据就存在主键索引的叶子节点里,所以回表 = 回到主键索引树查数据。
触发前提:查询走二级索引 + 返回列不全在该索引中 不触发场景:SELECT id, age FROM user WHERE age = 25(索引含
age和隐式主键
id) 典型错误现象:
EXPLAIN中
Extra字段只显示
Using where,没有
Using index
覆盖索引 = 让回表彻底消失
覆盖索引不是一种特殊索引类型,而是指“你查的每一列,都在同一个索引的叶子节点里存着”。只要满足这点,MySQL 就能直接从索引中返回全部结果,跳过回表。
实操关键点:
联合索引顺序很重要:INDEX idx_city_age_name (city, age, name)能覆盖
SELECT city, age, name,但不能覆盖
SELECT name, city(违反最左前缀) 主键自动包含:所有二级索引叶子节点都带主键值,所以
SELECT id, city只要
city在索引里,就一定覆盖 避免冗余列:别把
TEXT或超长
VARCHAR加进索引——索引体积暴涨,反而拖慢查询 验证方式:
EXPLAIN看
Extra是否出现
Using index(注意不是
Using index condition,后者仍需回表)
哪些情况看似能覆盖,其实还在回表?
容易被忽略的“伪覆盖”陷阱:
SELECT *永远无法被任何二级索引覆盖(除非你建了个包含所有字段的联合索引,但极不推荐) 使用函数或表达式:
SELECT UPPER(name) FROM user WHERE age = 25—— 即使
name在索引里,
UPPER()导致无法直接取值,仍需回表取原始
name再计算 隐式类型转换:
WHERE age = '25'(字符串)可能让优化器放弃索引,或导致索引只能用于查找、不能用于覆盖
ORDER BY非索引顺序:
SELECT city, age FROM user WHERE city = '北京' ORDER BY name—— 即使
city, age覆盖,但排序字段
name不在索引里,可能触发 filesort + 回表
实战中怎么快速判断和修复?
别靠猜,用
EXPLAIN FORMAT=TREE(MySQL 8.0+)或
EXPLAIN结合
SHOW INDEX对照看: 第一步:
EXPLAIN SELECT name, city FROM user WHERE age = 25;→ 看
key用了哪个索引,
Extra是什么 第二步:
SHOW INDEX FROM user WHERE Key_name = 'idx_age';→ 确认这个索引到底包含哪些列 第三步:如果发现缺列,补全索引:
ALTER TABLE user DROP INDEX idx_age, ADD INDEX idx_age_covering (age, name, city);注意:添加覆盖索引会增加写开销和磁盘占用,高频更新的列要谨慎;优先覆盖读多写少的查询路径
真正难的不是建索引,而是意识到:回表不是慢在 SQL 写得差,而是索引没对上查询需求。一个
Using index和一个
Using where,背后可能是 2 倍还是 10 倍的 I/O 差距。
