866 字
约 2 分钟
7
S11-J01 · 回表与覆盖索引
2024-12-31
2026-11-07

适用读者:想知道"为什么 select * 比 select id 慢"的人。
前置:S11-B05。
实测环境:MySQL 8.0.46 / InnoDB(s11lab,20,000 行,2026-11-05)。
覆盖旧文:840(84) 857(68) = 152 点。
覆盖扩展:E-S11-013(回表成本)、E-S11-014(覆盖索引判定)。
上一篇:S11-B06 · 事务与隔离级别
下一篇:S11-J02 · 索引失效的六种情况。


1. 一句话结论

走二级索引 → 拿到主键 → 再回主键索引取整行,这一步叫"回表"。如果查询的列全在索引里(覆盖索引),就跳过回表。

实测对照:

SQL Extra 含义
select age from t_user where age=30 Using index 覆盖索引,不回表
select name from t_user where age=30 NULL 需要回表(name 不在 idx_age 里)

两条 SQL 的 type/key/rows 完全一样(ref / idx_age / 250),差别只在 Extra——这就是覆盖索引的唯一标志。


2. 回表的代价(E-S11-013)

二级索引定位 → 得到主键(如 id=5)
   → 回主键索引(B+ 树)再查一次 → 取整行
代价 说明
多一次 B+ 树查找 每行一次
随机 IO 主键顺序与二级索引顺序常不一致
结果集越大越慢 回表次数 = 命中行数

"为什么 select * 慢"的完整答案:列越多,越不可能被索引覆盖 → 必然回表 → 行数多时随机 IO 放大。


3. 覆盖索引怎么造(E-S11-014)

做法 例子
只查索引列 select age from t where age=30
建联合索引 key idx_age_name(age, name) → select age,name where age=30 可覆盖
把高频列放进索引 需权衡索引体积与写代价

判据:"这个查询的列,能不能被某个索引完全包含?" 能 → 覆盖索引;不能 → 接受回表或改查询。

"select * 为什么是坏习惯":它几乎不可能被覆盖,并且会拉取不需要的大字段(text/blob)。


4. 联合索引与最左前缀的联动

key idx_age_name(age, name):

查询 能否覆盖
where age=30 → select age,name ✅
where age=30 → select name ✅(age 定位,name 在索引里)
where name='x' ❌ 最左前缀不满足

5. 五个误区

误区 真相
"Using index 表示用了索引" 表示覆盖索引(不一定是"用上索引",看 key)
"select * 只是多取几列" 会破坏覆盖索引 + 拉大字段
"所有索引查询都回表" 覆盖索引不回表
"索引列越多越好" 索引体积与写代价上升
"Extra 是 NULL 就没用索引" key 有值就用了,只是要回表

6. 自检

  1. 回表发生在哪一步?
  2. Using index 的确切含义?
  3. 三条 SQL 的 Extra 差异说明了什么?
  4. 为什么 select * 常导致回表?
  5. 联合索引如何服务于覆盖索引?

7. 下一篇

S11-J02 · 索引失效的六种情况

回表讲完,下一篇讲"索引为什么没被用上"——六种情况,全部有 EXPLAIN 对照。


本篇属于系列《S11 · 数据库与 SQL》。旧文来源:A-840/857;扩展点 E-S11-013/014。EXPLAIN 为 MySQL 8.0.46 实测。

S11-J01 · 回表与覆盖索引
https://www.clxhxhhr.top/posts/5331/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。