前言:为什么MySQL索引是面试必考题?

最近跳槽季,前后面了十几家公司,从中小厂到互联网大厂,每一轮技术面都问到了MySQL索引。

不是问B+树的原理,就是问索引失效的场景,再不就是SQL优化的实战。问法千奇百怪,但核心考的就那么几个点。

今天一篇把这些全讲透。不管你是准备面试还是日常工作优化,都能用上。

一、索引的本质:为什么需要索引?

用一句话说清楚索引是什么:

索引就是书的目录。没有索引,查数据就是一页页翻全书(全表扫描)。有了索引,直接翻到对应页码就能找到数据。

MySQL 里索引是在存储引擎层实现的,不是 Server 层。所以不同存储引擎的索引实现方式不同。我们最常用的 InnoDB,用的是 B+ 树 索引。

相比二叉搜索树、红黑树、哈希表,B+ 树有几个核心优势:

  • 树的高度低: 一个千万级表,B+ 树的高度一般是 3-4 层。意味着查一条数据最多只要 3-4 次磁盘 IO。
  • 范围查询高效: B+ 树的叶子节点通过双向链表相连,可以直接范围遍历。
  • 全节点存储数据: B+ 树的非叶子节点只存索引 key,不存数据,所以每个节点能存更多的 key,树更矮。

对比一下:如果用红黑树,高度大约是 25 层(2^25 ≈ 3300万),查一次要 25 次 IO,慢了一个数量级。

二、索引的分类

MySQL 索引可以从多个维度分类:

按数据结构分:

  • B+ 树索引: InnoDB 的默认索引类型,支持范围查询和排序
  • 哈希索引: Memory 引擎默认,等值查询极快,但不支持范围查询
  • 全文索引: 适用于大文本的模糊搜索,MyISAM 和 InnoDB 都支持

按逻辑功能分:

  • 主键索引: 每张表只能有一个,不能为 NULL,InnoDB 中就是聚簇索引
  • 唯一索引: 索引列的值必须唯一,但允许有一个 NULL
  • 普通索引: 没有任何限制,只是加速查询
  • 全文索引: 用于大文本的搜索
  • 组合索引: 多个列组合成一个索引,遵循最左前缀原则

按存储方式分(InnoDB):

  • 聚簇索引: 数据和索引存储在一起。InnoDB 中主键索引就是聚簇索引。叶子节点直接存储整行数据。
  • 二级索引(辅助索引): 叶子节点存储的是主键值。通过二级索引查数据需要回表(先查二级索引拿到主键,再通过主键索引查数据)。

三、联合索引的最左前缀原则

联合索引是面试最高频的考点之一,必须理解透彻。

假设我们建了一个联合索引 (a, b, c),那么:

能用到索引的查询:

  • WHERE a = 1
  • WHERE a = 1 AND b = 2
  • WHERE a = 1 AND b = 2 AND c = 3
  • WHERE a = 1 ORDER BY b — a 用索引,b 用索引排序
  • WHERE b = 2 AND a = 1 — MySQL 优化器会调整顺序,能用索引

不能用到索引的查询:

  • WHERE b = 2 — 没有 a,无法使用
  • WHERE c = 3 — 没有 a 也没有 b
  • WHERE a = 1 AND c = 3 — a 用到索引,但 c 用不到(跳过了 b)

简单记忆法: 就像爬楼梯,必须先上第一级,才能上第二级,不能跳过。从最左边开始,连续匹配才能用到索引。

四、索引失效的 8 种场景

下面这些场景是面试高频题,也是实际开发中容易踩的坑:

1. 对索引列使用函数

WHERE DATE(create_time) = '2026-05-08'  -- 索引失效
WHERE create_time >= '2026-05-08' AND create_time < '2026-05-09'  -- 索引生效

2. 隐式类型转换

-- user_id 是 varchar 类型
WHERE user_id = 12345  -- MySQL 会把 12345 转成字符串,能走索引
-- 但反过来,如果 user_id 是 int,传了字符串 '12345',也可能导致索引失效
-- 关键是看字段类型和传入类型是否一致

实际上这个要看具体情况。int 转 varchar 不走索引,varchar 转 int 可能走也可能不走,取决于数据。最好的做法是 类型保持一致

3. 不满足最左前缀

上面已经讲过了,联合索引不满足最左前缀直接失效。

4. 使用 LIKE 模糊匹配以 % 开头

WHERE name LIKE '%张三%'   -- 不走索引
WHERE name LIKE '张三%'    -- 走索引(范围查询)

5. 使用 OR 连接条件

WHERE a = 1 OR b = 2  -- 如果 a 和 b 都有索引,会走索引合并
-- 但如果其中一个没有索引,或者 MySQL 认为走索引还不如全表扫描快,就会走全表扫描

6. 索引列参与计算

WHERE price * 1.1 > 100  -- 不走索引
WHERE price > 100 / 1.1  -- 走索引(把计算移到右边)

7. 使用 NOT IN 和 !=

NOT IN 和 != 通常不走索引,因为 MySQL 认为这类查询返回的数据量可能很大,走索引还不如全表扫描快。但如果是 NOT EXISTS,有时候可以走索引。

8. 数据分布不均匀

如果索引列的数据分布严重不均匀(比如性别列,90% 都是男),MySQL 优化器可能会认为走索引还不如全表扫描快,就放弃索引了。

五、如何分析 SQL 是否走索引?

用 EXPLAIN 关键字:

EXPLAIN SELECT * FROM user WHERE age = 25;

重点关注几个字段:

  • type: 访问类型,从好到差依次是 system > const > eq_ref > ref > range > index > ALL。看到 ALL 说明全表扫描,需要优化。
  • key: 实际用到的索引名称。如果是 NULL,说明没用到索引。
  • rows: MySQL 估计需要扫描的行数。行数越少越好。
  • Extra: 额外信息。Using filesort 需要优化(不走索引排序),Using temporary 也要注意(用了临时表)。

六、常见 SQL 优化实战

场景 1:分页查询越往后越慢

SELECT * FROM order LIMIT 100000, 20;  -- 很慢

优化方案:

-- 方案一:记录上次查询的最大 ID
SELECT * FROM order WHERE id > 100000 LIMIT 20;

-- 方案二:子查询方式
SELECT * FROM order 
WHERE id >= (SELECT id FROM order LIMIT 100000, 1) 
LIMIT 20;

场景 2:ORDER BY 导致文件排序

SELECT * FROM user WHERE age > 18 ORDER BY create_time;  -- 可能 filesort

优化:建立联合索引 (age, create_time),让排序走索引。

场景 3:COUNT(*) 很慢

在 InnoDB 中,COUNT(*) 需要遍历索引统计行数。如果表很大,几个优化方法:

  • 用 COUNT(1) 代替 COUNT(*),性能差别不大
  • 单独建一张计数表,每次插入删除时更新
  • 用 EXPLAIN 估算行数(不精确但快)

场景 4:避免 SELECT *

SELECT * 会返回所有列,如果表有 text/blob 等大字段,会大幅增加 IO 开销。只查需要的列:

SELECT id, name, age FROM user WHERE ...;  -- 比 SELECT * 快很多

七、高频面试题

Q1:为什么 MySQL 用 B+ 树而不是 B 树?

B+ 树的非叶子节点不存数据,只存索引 key,所以每个节点可以存更多 key。同样数据量的情况下,B+ 树比 B 树更矮,IO 次数更少。而且 B+ 树的叶子节点通过链表相连,范围查询更高效。

Q2:什么是回表?怎么避免回表?

通过二级索引查到主键值后,再通过主键索引去查完整行数据,这个过程叫回表。

避免回表的方式是使用 覆盖索引——让查询的列全部包含在索引中。比如索引是 (name, age),查询 SELECT name, age FROM user WHERE name = '张三',直接在二级索引就拿到了数据,不需要回表。

Q3:一张表可以没有主键吗?

可以,但不推荐。如果 InnoDB 表没有显式定义主键,InnoDB 会选择一个非空的唯一索引作为主键。如果也没有唯一索引,InnoDB 会隐式生成一个 6 字节的 row_id 作为主键。但没有主键的表性能会明显下降。

Q4:什么是索引下推(ICP)?

MySQL 5.6 引入的优化。在联合索引中,如果有 WHERE 条件无法通过索引直接过滤,MySQL 5.6 之前需要把数据回表后再过滤。索引下推允许在存储引擎层就进行过滤,减少回表次数。

举例:索引 (city, age),查询 WHERE city = '北京' AND age > 30。5.6 之前:查到 city 为北京的所有记录 → 回表 → 过滤 age。5.6 之后:查到 city 为北京的记录时,直接在索引里过滤 age > 30,再回表,减少了回表次数。

Q5:数据量很大的表怎么加索引?

大表加索引会锁表,导致业务中断。建议的做法:

  • 在业务低峰期操作
  • 使用 pt-online-schema-change 工具(Percona Toolkit),通过触发器在不锁表的情况下完成 DDL
  • 或者用 gh-ost(GitHub 开源的在线 DDL 工具)

Q6:如何排查慢 SQL?

开启慢查询日志:

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;  -- 超过 1 秒的 SQL 记录到日志

然后分析慢查询日志,结合 EXPLAIN 分析,找到问题 SQL 进行优化。

写在最后

MySQL 索引的知识点看起来很多,但核心就三条:

1. 理解 B+ 树的结构和优势 —— 这是基础

2. 掌握联合索引的最左前缀原则 —— 这是面试高频

3. 学会用 EXPLAIN 分析 SQL —— 这是实战技能

这三条掌握了,MySQL 索引相关的问题基本都能应对。

如果你在面试中遇到过其他 MySQL 索引的刁钻问题,欢迎在评论区分享 👇

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐