MySQL 索引原理:面试官最爱问的 5 个问题

6 阅读6分钟

前言

MySQL 索引是后端面试的高频考点,也是实际工作中影响系统性能的关键因素。

这篇文章整理了面试官最爱问的 5 个索引问题,带你从原理到实战全面掌握。


问题一:索引是什么?为什么要用索引?

什么是索引?

打个比方:索引就像书的目录。

没有目录,你要找某个知识点,只能从第一页翻到最后一页(全表扫描)。

有了目录,你可以直接定位到页码(索引查找)。

索引的作用

优势劣势
大幅提高查询速度占用额外存储空间
加速 ORDER BY 和 GROUP BY降低写操作速度(需维护索引)
唯一索引保证数据唯一性建得太多反而适得其反

MySQL 索引的数据结构

MySQL 的 InnoDB 引擎使用 B+ 树 作为索引结构。

为什么是 B+ 树而不是 B 树?

  1. B+ 树所有数据都在 叶子节点,查询效率更稳定
  2. 叶子节点 用指针连接,范围查询更快
  3. 叶子节点 只存键值,一页能存更多索引项,树更矮
                    [20, 50]
                   /    |    \
          [10, 15]  [30, 40]  [60, 70]
             |         |         |
         [10→15→20→30→40→50→60→70]  ← 叶子节点链表

问题二:聚簇索引和非聚簇索引有什么区别?

这是索引原理的核心问题!

聚簇索引(Clustered Index)

聚簇索引 = 索引 + 数据 在一起

  • InnoDB 的主键就是聚簇索引
  • 叶子节点直接存储整行数据
  • 一张表只能有一个聚簇索引
聚簇索引结构:
主键值 → 完整行数据

例如:id=1 → {id:1, name:'阿牛', age:25, ...}

非聚簇索引(Secondary Index)

非聚簇索引 = 索引 + 主键

  • 除主键外的其他索引都是非聚簇索引
  • 叶子节点存储的是主键值
  • 查询需要「回表」才能获取完整数据
非聚簇索引结构:
索引列值 → 主键值

例如:name 索引 → name='阿牛' → id=1
然后通过 id=1 去聚簇索引查完整数据(回表)

回表是什么?

当你通过非主键索引查询时:

-- 假设有索引 idx_name(name)
SELECT * FROM user WHERE name = '阿牛';

执行过程:

  1. idx_name 索引树找到 name='阿牛',得到主键 id=1
  2. id=1 去聚簇索引查找完整数据

这就是 回表,需要两次 B+ 树查找。

如何避免回表?

覆盖索引:查询的列都在索引中,不需要回表。

-- 如果有联合索引 idx_name_age(name, age)
SELECT name, age FROM user WHERE name = '阿牛';
-- 直接从索引获取 name 和 age,无需回表!

问题三:什么情况下索引会失效?

这是面试必问!记住以下场景:

1. 对索引列进行函数操作

-- ❌ 索引失效
SELECT * FROM user WHERE YEAR(create_time) = 2024;

-- ✅ 正确写法
SELECT * FROM user WHERE create_time >= '2024-01-01' 
                      AND create_time < '2025-01-01';

2. 隐式类型转换

-- 假设 phone 是 varchar 类型,有索引
-- ❌ 索引失效(数字和字符串比较,发生隐式转换)
SELECT * FROM user WHERE phone = 13800138000;

-- ✅ 正确写法
SELECT * FROM user WHERE phone = '13800138000';

3. LIKE 以 % 开头

-- ❌ 索引失效
SELECT * FROM user WHERE name LIKE '%牛%';

-- ✅ 可以走索引
SELECT * FROM user WHERE name LIKE '牛%';

4. OR 条件中有非索引列

-- 假设只有 name 有索引
-- ❌ 索引失效
SELECT * FROM user WHERE name = '阿牛' OR age = 25;

-- ✅ 改成 UNION
SELECT * FROM user WHERE name = '阿牛'
UNION
SELECT * FROM user WHERE age = 25;

5. 联合索引不满足最左匹配

-- 假设有联合索引 idx_a_b_c(a, b, c)

-- ✅ 走索引
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3

-- ❌ 不走索引(跳过了 a)
WHERE b = 2 AND c = 3

-- ❌ 不走索引(跳过了 b)
WHERE a = 1 AND c = 3

6. != 或 <> 或 NOT IN

-- ❌ 索引可能失效
SELECT * FROM user WHERE status != 1;

-- ✅ 改成 IN 或范围查询
SELECT * FROM user WHERE status IN (0, 2, 3);

问题四:联合索引的设计原则是什么?

最左匹配原则

联合索引按照定义顺序生效,必须从最左列开始匹配。

-- 索引:idx_a_b_c(a, b, c)

-- 可以匹配的组合:
✅ aa, ba, b, c
✅ a, c(只有 a 生效,c 用于索引下推)

-- 不能匹配的组合:
❌ b
❌ c
❌ b, c

联合索引设计技巧

原则 1:区分度高的列放前面

-- 区分度公式:COUNT(DISTINCT column) / COUNT(*)

-- 假设 status 只有 0/1,而 name 各不相同
-- 应该把 name 放前面
CREATE INDEX idx_name_status ON user(name, status);

原则 2:覆盖常用查询

-- 如果经常查询:
SELECT name, age FROM user WHERE name = '阿牛';

-- 建立联合索引
CREATE INDEX idx_name_age ON user(name, age);
-- 走覆盖索引,无需回表

原则 3:考虑排序

-- 如果经常按 name 查询并按 age 排序
SELECT * FROM user WHERE name = '阿牛' ORDER BY age;

-- 索引顺序应该是
CREATE INDEX idx_name_age ON user(name, age);
-- 索引本身有序,避免 filesort

问题五:如何分析和优化索引?

使用 EXPLAIN 分析

EXPLAIN SELECT * FROM user WHERE name = '阿牛';

重点关注字段:

字段含义关注值
type访问类型system > const > eq_ref > ref > range > index > ALL
key使用的索引看是否走了期望的索引
rows预估扫描行数越少越好
Extra额外信息Using index(覆盖索引)是好事;Using filesort 需优化

type 从好到差

system → const → eq_ref → refrange → index → ALL
  最好                                           最差

常见优化场景

场景 1:慢查询优化

-- 发现慢查询
SELECT * FROM orders WHERE user_id = 123 AND status = 1;

-- 分析
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1;
-- 发现 type=ALL,全表扫描

-- 优化:添加联合索引
CREATE INDEX idx_user_status ON orders(user_id, status);

-- 再次分析
EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 1;
-- type=ref,走了索引

场景 2:避免重复索引

-- ❌ 冗余索引
INDEX idx_a (a)
INDEX idx_a_b (a, b)  -- idx_a 是多余的

-- ✅ 只保留
INDEX idx_a_b (a, b)

索引维护

-- 查看表索引
SHOW INDEX FROM user;

-- 分析索引使用情况
SELECT * FROM sys.schema_unused_indexes;

-- 重建索引(解决索引碎片)
ALTER TABLE user ENGINE=InnoDB;

总结

这篇文章解答了 5 个索引核心问题:

索引原理:B+ 树结构,聚簇索引 vs 非聚簇索引 ✅ 索引失效场景:函数、隐式转换、LIKE %开头、OR、跳过最左列 ✅ 联合索引设计:最左匹配、区分度、覆盖索引 ✅ 索引优化方法:EXPLAIN 分析、type 判断、rows 关注

面试回答模板

"MySQL InnoDB 使用 B+ 树作为索引结构。聚簇索引的叶子节点存储完整数据,非聚簇索引存储主键值需要回表。设计索引要考虑区分度、覆盖查询、避免索引失效场景。可以通过 EXPLAIN 分析执行计划,关注 type 和 rows 字段。"


扩展阅读


💡 面试锦囊:索引是后端面试必考题,建议结合实际项目经验回答,比如"我们之前有个查询慢的问题,通过添加联合索引从 2 秒优化到 20 毫秒"。

下一期:《Redis 缓存穿透、击穿、雪崩:一次讲清楚》,敬请期待