Skip to content

MySQL 索引面试通关指南 ​

本文完全按照真实面试场景编排,按从高频到低频的顺序,带你手撕 MySQL 索引相关的各个核心考点。


❓ 面试官:MySQL 为什么默认使用 B+ 树作为索引结构?而不是红黑树或 B 树? ​

频率:🔥🔥🔥🔥🔥

💡 一句话总结(先抛结论): 因为 B+ 树的层级更低,磁盘 IO 次数更少,而且叶子节点形成了双向链表,非常适合关系型数据库中最常见的范围查询。

📝 详细原理解析:

  1. 相比红黑树/二叉树:二叉树每个节点只有两个分支,数据量大时树会非常高。数据库索引存储在磁盘上,树的每一层通常对应一次磁盘 IO,导致磁盘 IO 次数过多。B+ 树是多路平衡查找树,通常 3-4 层就能支撑千万级数据。
  2. 相比 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 等字段建的索引就是非聚簇索引。

回表过程举例:

sql
SELECT * FROM users WHERE email = 'test@example.com';
  1. 引擎先在 idx_email 这一棵 B+ 树上找到 test@example.com 对应的主键 ID(比如 id = 5)。
  2. 引擎拿着 id = 5,再去主键的 B+ 树(聚簇索引)中查找,获取这一行的完整数据。这个过程就是回表。

🌟 面试加分项(实战经验): 因为回表需要多查一棵树,所以性能会有损耗。在开发中,对于高频的查询,我通常会尽量使用覆盖索引来避免回表。


❓ 面试官:刚才提到了覆盖索引,能详细解释下吗? ​

频率:🔥🔥🔥🔥

💡 一句话总结(先抛结论): 覆盖索引并不是一种具体的索引类型,而是一种查询优化手段。当我们要查询的列,都已经被包含在当前使用的索引中时,就不需要回表了,这就叫覆盖索引。

📝 详细原理解析: 假设我们有联合索引 idx_username_email (username, email)。

sql
-- 触发覆盖索引,无需回表
SELECT id, username, email FROM users WHERE username = '张三';

因为联合索引的叶子节点里,本身就存了 username、email 以及主键 id。我们要查的这三个字段全都在索引树里,直接返回即可(执行计划 Extra 会显示 Using index)。

sql
-- 需要回表
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:

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 条件中有未建索引的列。

📝 详细原理解析:

  1. 使用了函数/计算:
    • 失效:WHERE DATE(create_time) = '2024-01-01'(因为 B+ 树存的是原始值,用了函数就对应不上了)
    • 优化:WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02'
  2. 隐式类型转换:
    • 失效:WHERE phone = 13800138000(表里 phone 是 varchar,传了 int,MySQL 会默认对列加 CAST 函数转换为整型,导致失效)
  3. 左模糊查询:
    • 失效:WHERE name LIKE '%张三'(B+ 树是按从左到右排序的,最左边是通配符就无法走树查找)
  4. OR 条件错用:
    • WHERE id = 1 OR remark = 'test'(如果 remark 没有索引,即使 id 有索引,也会全表扫描)

🌟 面试加分项(实战经验): 面试官其实很喜欢问隐式类型转换。在实际开发中,如果联表查询 JOIN 性能极差,我首先会检查关联的两个字段类型、长度、字符集排序规则(Collation)是否完全一致。如果有任何一个不一致,就会触发隐式转换,导致被驱动表索引失效,这是线上非常容易踩坑的地方。