参考:高性能 MySQL(第 4 版) · MySQL 技术内幕:InnoDB 存储引擎(第 2 版) · MySQL 8.4 Reference Manual。每章含可暂停 Lab 动画 + 面试问答 + 书籍章节对照。
MySQL 面试 · Lab A · 索引与查询路径
索引与查询路径
主问题
SELECT * FROM user WHERE name = 'Tom'这条查询为什么会回表?
这题真正考什么
- · 不是考你背「回表」这个词。
- · 而是考你能不能画出 InnoDB 二级索引到聚簇索引的查询路径。
学完你要能做到
- · 画出 secondary index → primary index 的回表路径
- · 解释 SELECT * 为什么回表
- · 解释 SELECT id,name 为什么不回表
- · 区分回表、覆盖索引、索引下推、最左前缀
二级索引先按 name 找到 Tom,但叶子节点只带主键id=10。
SELECT * 需要完整行,所以还要用 id=10 去PRIMARY 聚簇索引取完整行 —— 这一步就是回表。
如果只查 id,name,字段已经被 name_idx覆盖,就不需要回表。
学习路线
点节点跳转 · 滚动时自动高亮当前位置
这道题到底在问什么?
表面上问「为什么回表」,实际是在问:MySQL 通过二级索引查到主键之后,为什么不能直接返回完整结果。
SELECT * FROM user WHERE name = 'Tom'面试官嘴上问
为什么会回表?
其实在考
二级索引拿到主键后,凭什么还要再查一次?
先补 3 个概念
把下面三个词分清楚,后面的路径图就能自己画出来。
聚簇索引 PRIMARY
叶子节点 = 完整行
按主键组织数据,叶子节点直接保存整行所有列。找到主键就等于找到完整数据。
二级索引 name_idx
叶子节点 = name + id
叶子只保存「索引列 + 主键」。命中后只能拿到主键,不含其它列。
回表
二级 → 聚簇 再查一次
先查二级索引拿到主键,再拿主键去聚簇索引取完整行 —— 这第二次查找就是回表。
先拆 SQL
任何索引题,先把一条 SQL 拆成「访问路径」和「投影列」两半。
SELECT * FROM user WHERE name = 'Tom';
WHERE name = 'Tom'→SELECT *→SELECT * 为什么会回表?
跟着下面四步走一遍,每一步右侧都绑定了「发生了什么 / 为什么 / 面试怎么答 / 常见误区」。
二级索引 B+Tree(name_idx)
键:name,值:主键 id
拿 id=10
再查整行
聚簇索引 B+Tree(PRIMARY)
键:主键 id,值:整行数据
当前发生了什么
优化器选中二级索引 name_idx,在它的 B+Tree 上按 name='Tom' 一路走到叶子节点。
为什么会这样
name 是二级索引列,等值条件可以在有序的 B+Tree 上直接定位,type=ref。
面试怎么回答
先说访问路径:WHERE name='Tom' 命中 name_idx,走二级索引等值查找。
常见误区
命中索引 ≠ 已经拿到数据,此刻叶子里还没有完整行。
SELECT id,name 为什么不回表?
同样命中 name_idx,只因为投影列不同,路径就分叉了。
SELECT *需要完整行- 1name_idx 命中 (name='Tom', id=10)
- 2回表:拿 id=10 查 PRIMARY
- 3聚簇索引取完整行 → 返回
SELECT id, name字段已被覆盖- 1name_idx 命中 (name='Tom', id=10)
- 2id、name 都在叶子里
- 3直接返回,无需回表
联合索引怎么判断?
以idx_name_age_city(name, age, city)为例,看每条 WHERE 能用到哪几列。
| 查询条件 | 能用到的列 |
|---|---|
| WHERE name='Tom' | name |
| WHERE name='Tom' AND age=18 | name, age |
| WHERE age=18 | (基本用不上) |
| WHERE name='Tom' AND age>18 AND city='BJ' | name, age |
易混概念对比
四个最容易说混的词,按「解决什么 / 关键词 / 是否回表」一次对齐。
| 概念 | 解决什么问题 | 是否可能回表 |
|---|---|---|
| 回表 | 二级索引只有主键,需要再查聚簇索引补齐完整行 | 本身就是回表 |
| 覆盖索引 | 查询列都在同一个索引里,省掉聚簇索引那一次查找 | 不回表 |
| 索引下推 ICP | 在存储引擎层用索引里的其它列先过滤,减少回表次数 | 减少但不消除 |
| 最左前缀 | 决定联合索引能从左到右用到哪几列 | 取决于是否被覆盖 |
面试回答生成器
同一道题,按时间预算选一档念出来。
二级索引 name_idx 的叶子只存 name 和主键 id=10,SELECT * 需要完整行,所以还要拿 id=10 回 PRIMARY 聚簇索引取整行,这一步就是回表。
如果只查 id,name,字段已经被 name_idx 覆盖,就不需要回表。
追问链 + 复习清单
勾掉下面每一条,就算这道题真正通关。
通关检查
0/5 已掌握追问链
- Q主键是 UUID 会怎样影响二级索引体积和页分裂?
- QSELECT id,name 和 SELECT * 的 EXPLAIN Extra 可能有什么区别?
- Q索引下推和覆盖索引分别减少了哪一部分成本?