MySQL 索引面试通关指南
本文完全按照真实面试场景编排,按从高频到低频的顺序,带你手撕 MySQL 索引相关的各个核心考点。
❓ 面试官:MySQL 为什么默认使用 B+ 树作为索引结构?而不是红黑树或 B 树?
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 因为 B+ 树的层级更低,磁盘 IO 次数更少,而且叶子节点形成了双向链表,非常适合关系型数据库中最常见的范围查询。
📝 详细原理解析:
- 相比红黑树/二叉树:二叉树每个节点只有两个分支,数据量大时树会非常高。数据库索引存储在磁盘上,树的每一层通常对应一次磁盘 IO,导致磁盘 IO 次数过多。B+ 树是多路平衡查找树,通常 3-4 层就能支撑千万级数据。
- 相比 B 树:
- B 树的所有节点都存完整的数据(记录或行指针),导致一页(默认16KB)能存的索引键很少,树变高,IO 变多。
- B 树不支持范围查询,而 B+ 树只有叶子节点存数据,非叶子节点只存主键和指针,一页能装下极多的键。最关键的是,B+ 树的叶子节点有双向链表,
SELECT * WHERE id > 10时只需要找到 10,然后顺着链表向后遍历即可。
🌟 面试加分项(实战经验): 在实际业务中,由于 B+ 树一页是 16KB,我们在设计表时,我会尽量让主键不要太大(比如尽量用自增 BIGINT/雪花算法,而不是无序的长字符串 UUID)。因为主键越短,一个数据页里能装下的索引键就越多,树的高度就会更低,查询速度就越快。同时,因为 InnoDB 二级索引(非聚簇索引)的叶子节点存的是主键值,主键短也能节省大量二级索引的存储空间。
❓ 面试官:能说说你对聚簇索引和非聚簇索引的理解吗?什么是回表?
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 聚簇索引的数据和索引是保存在一起的(叶子节点就是完整的数据行),而非聚簇索引(二级索引)的叶子节点只保存了主键值。通过非聚簇索引查到主键后,再回到聚簇索引中去查完整数据的过程,就叫回表。
📝 详细原理解析:
- 聚簇索引(Clustered Index):InnoDB 引擎的表必须有且只有一个聚簇索引,默认是主键。如果没有主键,会选择第一个非空的唯一索引;如果都没有,InnoDB 会自动生成一个隐式的 6 字节 ROW_ID 作为聚簇索引。
- 非聚簇索引(Secondary Index):我们平时针对
username、phone等字段建的索引就是非聚簇索引。
回表过程举例:
SELECT * FROM users WHERE email = 'test@example.com';- 引擎先在
idx_email这一棵 B+ 树上找到test@example.com对应的主键 ID(比如 id = 5)。 - 引擎拿着
id = 5,再去主键的 B+ 树(聚簇索引)中查找,获取这一行的完整数据。这个过程就是回表。
🌟 面试加分项(实战经验): 因为回表需要多查一棵树,所以性能会有损耗。在开发中,对于高频的查询,我通常会尽量使用覆盖索引来避免回表。
❓ 面试官:刚才提到了覆盖索引,能详细解释下吗?
频率:🔥🔥🔥🔥
💡 一句话总结(先抛结论): 覆盖索引并不是一种具体的索引类型,而是一种查询优化手段。当我们要查询的列,都已经被包含在当前使用的索引中时,就不需要回表了,这就叫覆盖索引。
📝 详细原理解析: 假设我们有联合索引 idx_username_email (username, email)。
-- 触发覆盖索引,无需回表
SELECT id, username, email FROM users WHERE username = '张三';因为联合索引的叶子节点里,本身就存了 username、email 以及主键 id。我们要查的这三个字段全都在索引树里,直接返回即可(执行计划 Extra 会显示 Using index)。
-- 需要回表
SELECT id, username, email, phone FROM users WHERE username = '张三';因为 phone 字段不在索引中,只能拿着 ID 回表去聚簇索引里拿 phone。
🌟 面试加分项(实战经验): 这也是为什么我们规约里严禁使用 SELECT * 的核心原因之一。SELECT * 几乎一定会导致回表。在实现某些分页列表或下拉框接口时,如果只返回 ID 和 Name,我一定会针对这两个字段建联合索引,利用覆盖索引把接口响应时间压到极低。
❓ 面试官:什么是最左前缀匹配原则?
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 在使用联合索引时,MySQL 会按照联合索引创建的顺序,从左到右依次匹配,如果遇到范围查询(>、<、between、like)就会停止匹配后面的列。
📝 详细原理解析: 假设创建了联合索引 (a, b, c),其实相当于创建了 (a)、(a, b)、(a, b, c) 三个索引。
where a = 1 and b = 2 and c = 3:完全走索引。where a = 1 and c = 3:只有a走索引,c无法走索引(因为跳过了b)。where a > 1 and b = 2:a走索引范围扫描,b无法走索引(因为a是范围查询)。where b = 2 and c = 3:完全不走索引(没有最左侧的a)。
🌟 面试加分项(实战经验):
- 顺序无关性:
where b = 2 and a = 1这种乱序的 SQL,MySQL 优化器会自动将其优化为a = 1 and b = 2,所以能正常走索引。 - 建索引的诀窍:在设计联合索引时,我会把区分度最高(基数最大)的字段放在最左边。另外,如果已经有了
(a, b)索引,且业务频繁需要查a,就不需要再单独建单列索引(a)了,避免冗余。
❓ 面试官:什么是索引下推(ICP)?
频率:🔥🔥🔥
💡 一句话总结(先抛结论): 索引下推(Index Condition Pushdown)是 MySQL 5.6 引入的优化机制。它把原本应该在 Server 层进行的回表过滤操作,"下推"到了引擎层(InnoDB)在遍历索引时提前过滤,从而减少回表的次数。
📝 详细原理解析: 假设有联合索引 (name, age),执行 SQL:
SELECT * FROM users WHERE name LIKE '张%' AND age = 25;根据最左前缀原则,name 是范围查询,age 本来是无法走索引的。
- 没有 ICP(5.6 之前):存储引擎根据
张%找到 100 条符合的主键 ID,然后回表 100 次把完整记录取出来,返回给 Server 层,Server 层再去判断age = 25。 - 有 ICP(5.6 之后):存储引擎在索引树上扫描时,发现虽然
age没法用来精确定位,但索引树里明明有age的值!于是它在索引树里直接判断age是否等于 25。假设 100 个人里只有 2 个25岁,那就只需要回表 2 次。
(执行计划 Extra 中看到 Using index condition 就代表触发了索引下推)。
❓ 面试官:你能说出几个常见的索引失效场景吗?
频率:🔥🔥🔥🔥🔥
💡 一句话总结(先抛结论): 最常见的索引失效场景包括:对索引列使用函数或计算、发生隐式类型转换、LIKE 左模糊查询、不等于(!=、<>)以及 OR 条件中有未建索引的列。
📝 详细原理解析:
- 使用了函数/计算:
- 失效:
WHERE DATE(create_time) = '2024-01-01'(因为 B+ 树存的是原始值,用了函数就对应不上了) - 优化:
WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02'
- 失效:
- 隐式类型转换:
- 失效:
WHERE phone = 13800138000(表里 phone 是 varchar,传了 int,MySQL 会默认对列加CAST函数转换为整型,导致失效)
- 失效:
- 左模糊查询:
- 失效:
WHERE name LIKE '%张三'(B+ 树是按从左到右排序的,最左边是通配符就无法走树查找)
- 失效:
- OR 条件错用:
WHERE id = 1 OR remark = 'test'(如果 remark 没有索引,即使 id 有索引,也会全表扫描)
🌟 面试加分项(实战经验): 面试官其实很喜欢问隐式类型转换。在实际开发中,如果联表查询 JOIN 性能极差,我首先会检查关联的两个字段类型、长度、字符集排序规则(Collation)是否完全一致。如果有任何一个不一致,就会触发隐式转换,导致被驱动表索引失效,这是线上非常容易踩坑的地方。