866 字
约 2 分钟
7
S11-J01 · 回表与覆盖索引
适用读者:想知道"为什么
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. 自检
- 回表发生在哪一步?
Using index的确切含义?- 三条 SQL 的
Extra差异说明了什么? - 为什么
select *常导致回表? - 联合索引如何服务于覆盖索引?
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/ 评论
0 条
还没有评论,先写一条吧。