D
RuntimeScope/MySQL 面试专题/index-path/索引与查询路径

参考:高性能 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 为什么不回表
  • · 区分回表、覆盖索引、索引下推、最左前缀
30 秒答案先记结论,再看推导

二级索引先按 name 找到 Tom,但叶子节点只带主键id=10

SELECT * 需要完整行,所以还要用 id=10 去PRIMARY 聚簇索引取完整行 —— 这一步就是回表。

如果只查 id,name,字段已经被 name_idx覆盖,就不需要回表。

学习路线

点节点跳转 · 滚动时自动高亮当前位置

01

这道题到底在问什么?

表面上问「为什么回表」,实际是在问:MySQL 通过二级索引查到主键之后,为什么不能直接返回完整结果

SELECT * FROM user WHERE name = 'Tom'

面试官嘴上问

为什么会回表?

其实在考

二级索引拿到主键后,凭什么还要再查一次?

02

先补 3 个概念

把下面三个词分清楚,后面的路径图就能自己画出来。

聚簇索引 PRIMARY

叶子节点 = 完整行

按主键组织数据,叶子节点直接保存整行所有列。找到主键就等于找到完整数据。

二级索引 name_idx

叶子节点 = name + id

叶子只保存「索引列 + 主键」。命中后只能拿到主键,不含其它列。

回表

二级 → 聚簇 再查一次

先查二级索引拿到主键,再拿主键去聚簇索引取完整行 —— 这第二次查找就是回表。

03

先拆 SQL

任何索引题,先把一条 SQL 拆成「访问路径」和「投影列」两半。

SELECT * FROM user WHERE name = 'Tom';
WHERE name = 'Tom'
决定访问路径name 是二级索引列 → 用 name_idx 定位。
SELECT *
决定是否要完整行要所有列 → 二级索引叶子不够用。
结论name_idx 里不包含完整行,所以这条 SQL 必须回表。
04

SELECT * 为什么会回表?

跟着下面四步走一遍,每一步右侧都绑定了「发生了什么 / 为什么 / 面试怎么答 / 常见误区」。

二级索引 B+Tree(name_idx)

键:name,值:主键 id

...
...
...
...
...
Leo
id=3
Mike
id=7
Tom
id=10
Tony
id=12
Zoe
id=15
回表
拿 id=10
再查整行

聚簇索引 B+Tree(PRIMARY)

键:主键 id,值:整行数据

...
...
...
...
...
id=8
Mike 16 ...
id=10
Tom 18 ...
id=11
Jerry 20 ...
id=12
Tony 22 ...
...

当前发生了什么

优化器选中二级索引 name_idx,在它的 B+Tree 上按 name='Tom' 一路走到叶子节点。

为什么会这样

name 是二级索引列,等值条件可以在有序的 B+Tree 上直接定位,type=ref。

面试怎么回答

先说访问路径:WHERE name='Tom' 命中 name_idx,走二级索引等值查找。

常见误区

命中索引 ≠ 已经拿到数据,此刻叶子里还没有完整行。

结论回表 = 二级索引拿到主键后,再用主键去聚簇索引取一次完整行。整条 SQL 走了两棵 B+Tree。
05

SELECT id,name 为什么不回表?

同样命中 name_idx,只因为投影列不同,路径就分叉了。

SELECT *需要完整行
  1. 1name_idx 命中 (name='Tom', id=10)
  2. 2回表:拿 id=10 查 PRIMARY
  3. 3聚簇索引取完整行 → 返回
SELECT id, name字段已被覆盖
  1. 1name_idx 命中 (name='Tom', id=10)
  2. 2id、name 都在叶子里
  3. 3直接返回,无需回表
结论是否回表,不是看 WHERE 是否命中索引,而是看 SELECT 查询字段是否被同一个索引覆盖
06

联合索引怎么判断?

idx_name_age_city(name, age, city)为例,看每条 WHERE 能用到哪几列。

查询条件能用到的列
WHERE name='Tom'name
WHERE name='Tom' AND age=18name, age
WHERE age=18(基本用不上)
WHERE name='Tom' AND age>18 AND city='BJ'name, age
结论联合索引不是「字段出现就能用」,而是看能否从左到右形成连续查找路径。一旦遇到范围列,后面的列就无法继续做精确定位。
07

易混概念对比

四个最容易说混的词,按「解决什么 / 关键词 / 是否回表」一次对齐。

概念解决什么问题是否可能回表
回表二级索引只有主键,需要再查聚簇索引补齐完整行本身就是回表
覆盖索引查询列都在同一个索引里,省掉聚簇索引那一次查找不回表
索引下推 ICP在存储引擎层用索引里的其它列先过滤,减少回表次数减少但不消除
最左前缀决定联合索引能从左到右用到哪几列取决于是否被覆盖
08

面试回答生成器

同一道题,按时间预算选一档念出来。

二级索引 name_idx 的叶子只存 name 和主键 id=10,SELECT * 需要完整行,所以还要拿 id=10 回 PRIMARY 聚簇索引取整行,这一步就是回表。

如果只查 id,name,字段已经被 name_idx 覆盖,就不需要回表。

09

追问链 + 复习清单

勾掉下面每一条,就算这道题真正通关。

通关检查

0/5 已掌握

追问链

  • Q主键是 UUID 会怎样影响二级索引体积和页分裂?
  • QSELECT id,name 和 SELECT * 的 EXPLAIN Extra 可能有什么区别?
  • Q索引下推和覆盖索引分别减少了哪一部分成本?