索引原理与优化

索引是数据库性能优化的关键,理解索引原理是写出高效 SQL 的基础。

索引的基本概念

什么是索引?

索引是帮助 MySQL 高效获取数据的数据结构。可以类比为书籍的目录:

没有索引:从第一页开始翻,直到找到目标内容(全表扫描)
有索引:先查目录,直接定位到目标页码(索引查找)

为什么需要索引?

场景:在 1000 万条数据中查找一条记录

-- 没有索引
SELECT * FROM users WHERE phone = '13800138000';
-- 全表扫描:扫描 1000 万行,耗时数秒甚至数分钟

-- 有索引
SELECT * FROM users WHERE phone = '13800138000';
-- 索引查找:定位到几行数据,耗时毫秒级

索引的代价

优点
代价

加快查询速度

占用额外存储空间

加快排序速度

降低写入性能(INSERT/UPDATE/DELETE)

加快分组速度

需要维护索引结构

结论:索引不是越多越好,需要权衡利弊。


索引数据结构 ⭐⭐⭐⭐⭐

B+ Tree 索引

InnoDB 使用 B+ Tree 作为默认索引结构,这是面试必考点。

B+ Tree 的结构

B+ Tree 的特点

特点
说明

所有数据在叶子节点

非叶子节点只存索引键,不存数据

叶子节点形成双向链表

支持高效的范围查询

非叶子节点可存更多键

降低树高,减少 IO 次数

查询复杂度稳定

所有查询都要走到叶子节点,O(log n)

为什么选择 B+ Tree?⭐⭐⭐⭐⭐

为什么不用 B Tree?

为什么不用红黑树(平衡二叉树)?

为什么不用哈希表?

B+ Tree 索引的查找过程

一页能存多少数据?

Hash 索引

Hash 索引的特点

使用场景

场景
是否适合 Hash 索引

等值查询(WHERE id = 10)

✅ 适合

范围查询(WHERE id > 10)

❌ 不适合

排序(ORDER BY id)

❌ 不适合

前缀匹配(WHERE name LIKE 'abc%')

❌ 不适合

InnoDB 中的 Hash

Memory 引擎支持 Hash 索引


索引类型

主键索引(Primary Key)⭐⭐⭐⭐⭐

主键索引是一种特殊的唯一索引,每个表只能有一个。

主键选择建议

方案
优点
缺点

自增主键

顺序插入,性能好

分布式环境有问题

UUID

全局唯一

无序,插入性能差

业务主键

业务含义明确

可能需要修改,有风险

自增主键的优势

唯一索引(Unique Index)

唯一索引确保列值不重复。

唯一索引 vs 普通索引

特性
唯一索引
普通索引

值可重复

插入检查

需要检查唯一性

不需要

插入性能

稍慢

稍快

Change Buffer

不能使用

可以使用

普通索引(Normal Index)

最基本的索引类型,没有任何限制。

全文索引(Full-Text Index)

用于全文搜索,适合大文本字段。

组合索引(Composite Index)⭐⭐⭐⭐⭐

组合索引是在多个列上创建的索引。

最左前缀原则 ⭐⭐⭐⭐⭐

组合索引遵循最左前缀原则,这是面试高频考点。

最左前缀原则的原因

索引下推(Index Condition Pushdown, ICP)

MySQL 5.6 引入的优化,减少回表次数。


聚簇索引与非聚簇索引 ⭐⭐⭐⭐⭐

这是 InnoDB 索引的核心概念,必须深入理解。

聚簇索引(Clustered Index)

定义:数据和索引存储在一起的索引。InnoDB 的主键索引就是聚簇索引。

聚簇索引的特点

特点
说明

一个表只能有一个

数据只能按一种方式排序存储

叶子节点存完整行数据

找到索引就找到了数据

主键查询效率高

不需要回表

顺序插入效率高

自增主键最佳

InnoDB 聚簇索引的选择规则

非聚簇索引(Secondary Index / 二级索引)

定义:叶子节点存储的是主键值,而不是完整行数据。

回表查询 ⭐⭐⭐⭐⭐

通过非聚簇索引查询时,如果需要的列不在索引中,就需要回表

回表的代价

  • 多一次 B+ Tree 查找

  • 如果回表数据量大,性能下降明显

覆盖索引 ⭐⭐⭐⭐⭐

如果查询的列都在索引中,就不需要回表,称为覆盖索引

覆盖索引优化示例

索引结构对比总结

特性
聚簇索引
非聚簇索引

叶子节点内容

完整行数据

主键值

数量限制

一个表只有一个

可以有多个

查询效率

高(不需要回表)

可能需要回表

插入效率

取决于主键顺序

取决于索引列顺序

存储方式

数据即索引

索引和数据分离


索引优化 ⭐⭐⭐⭐⭐

索引失效场景

了解哪些情况会导致索引失效,是写出高效 SQL 的关键。

1. 对索引列使用函数或表达式

原因:对列使用函数后,需要对每行数据计算函数值,无法利用 B+ Tree 的有序性。

2. 隐式类型转换

类型转换规则

3. 前导模糊查询

原因:B+ Tree 索引是按字符顺序排序的,% 在前面无法定位起始位置。

4. OR 条件(部分列无索引)

5. 不等于条件

原因:不等于条件可能匹配大量数据,此时全表扫描可能比索引更快。

6. IS NULL / IS NOT NULL

建议:尽量避免 NULL 值,使用默认值代替。

7. 联合索引不满足最左前缀

索引设计原则

1. 选择性高的列建索引

选择性 = 不重复值数量 / 总行数

2. 考虑查询频率和方式

3. 避免冗余索引

4. 考虑索引的维护成本

5. 使用前缀索引

对于很长的字符串列,可以只索引前缀部分。

前缀索引的限制

  • 无法用于 ORDER BY

  • 无法用于覆盖索引

6. 利用覆盖索引

索引优化实战示例


执行计划分析 ⭐⭐⭐⭐⭐

EXPLAIN 基础用法

EXPLAIN 输出字段详解

id - 查询序列号

select_type - 查询类型

含义

SIMPLE

简单查询(不含子查询或 UNION)

PRIMARY

最外层查询

SUBQUERY

子查询中的第一个 SELECT

DERIVED

派生表(FROM 子句中的子查询)

UNION

UNION 中第二个及以后的 SELECT

type - 访问类型 ⭐⭐⭐⭐⭐

性能从好到差

key_len - 索引长度

用于判断使用了组合索引的哪些列。

rows - 预估扫描行数

Extra - 额外信息 ⭐⭐⭐⭐

含义
说明

Using index

覆盖索引

好,不需要回表

Using where

使用 WHERE 过滤

正常

Using index condition

索引下推(ICP)

Using temporary

使用临时表

需要优化

Using filesort

文件排序

需要优化

Using join buffer

使用连接缓冲

JOIN 无索引

优化示例

执行计划分析实战


面试高频问题 ⭐⭐⭐⭐⭐

Q1: 为什么 MySQL 选择 B+ Tree 作为索引结构?

答案要点

  1. 对比 B Tree

    • B+ Tree 非叶子节点不存数据,能存更多键

    • B+ Tree 更矮,IO 次数更少

    • B+ Tree 叶子节点形成链表,范围查询更高效

  2. 对比红黑树

    • 红黑树是二叉树,层数太高

    • 1000 万数据,红黑树约 24 层,B+ Tree 约 3-4 层

  3. 对比哈希表

    • 哈希表不支持范围查询

    • 哈希表不支持排序

    • 哈希表不支持最左前缀


Q2: 什么是聚簇索引和非聚簇索引?

答案要点

特性
聚簇索引
非聚簇索引

叶子节点

存完整行数据

存主键值

数量

一个表只有一个

可以有多个

查询

不需要回表

可能需要回表

InnoDB

主键索引

非主键索引


Q3: 什么是回表?如何避免?

答案要点

  1. 回表:通过非聚簇索引查询时,需要再去聚簇索引获取完整数据

  2. 避免方法:使用覆盖索引,让查询的列都在索引中


Q4: 什么是最左前缀原则?

答案要点

组合索引 (a, b, c) 只能按从左到右的顺序使用:

  • WHERE a = 1

  • WHERE a = 1 AND b = 2

  • WHERE b = 2 ❌(没有最左列 a)

原因:B+ Tree 按 (a, b, c) 顺序排序,单独看 b 或 c 是无序的。


Q5: 索引失效的常见场景有哪些?

答案要点

  1. 对索引列使用函数或表达式

  2. 隐式类型转换

  3. 前导模糊查询(LIKE '%abc'

  4. OR 条件中有无索引的列

  5. 不等于条件(!=, <>

  6. 联合索引不满足最左前缀


Q6: 如何选择合适的索引?

答案要点

  1. 选择性高的列:不重复值多的列

  2. 经常查询的列:WHERE、ORDER BY、GROUP BY 中的列

  3. 避免冗余:(a, b) 索引可覆盖 (a) 的功能

  4. 考虑写入成本:写多读少的表少建索引

  5. 利用覆盖索引:减少回表


Q7: EXPLAIN 中哪些情况需要优化?

答案要点

问题
表现
优化方向

全表扫描

type: ALL

添加合适的索引

文件排序

Extra: Using filesort

给排序列加索引

使用临时表

Extra: Using temporary

优化 GROUP BY

扫描行数多

rows 值很大

优化索引或 SQL


Q8: 自增主键有什么优势?

答案要点

  1. 顺序插入:新数据追加在末尾,不需要移动其他数据

  2. 避免页分裂:随机主键会导致频繁页分裂

  3. 占用空间小:INT/BIGINT 比 UUID 小

  4. 查询效率高:整数比较比字符串比较快


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

答案要点

MySQL 5.6 引入的优化,将部分 WHERE 条件下推到存储引擎层判断,减少回表次数。


Q10: 前缀索引有什么限制?

答案要点

  1. 无法 ORDER BY:前缀索引不包含完整值

  2. 无法覆盖索引:无法确定完整值

  3. 选择性可能降低:前缀太短会增加重复


总结

核心要点 ⭐⭐⭐⭐⭐

1. 索引数据结构

  • B+ Tree:支持范围查询、排序,3-4 层可存千万数据

  • 非叶子节点只存键,叶子节点存数据并形成链表

2. 聚簇 vs 非聚簇

  • 聚簇索引:数据和索引在一起,一个表只有一个

  • 非聚簇索引:叶子节点存主键值,可能需要回表

3. 索引优化

  • 最左前缀原则

  • 覆盖索引避免回表

  • 避免索引失效场景

4. 执行计划

  • type 至少 range,避免 ALL

  • 注意 Using filesort 和 Using temporary

记住这些关键点

  • B+ Tree 比 B Tree 更适合数据库(更矮、范围查询更快)

  • 聚簇索引的叶子节点存完整行数据

  • 非聚簇索引需要回表,覆盖索引不需要

  • 组合索引遵循最左前缀原则

  • 对索引列使用函数会导致索引失效

  • 自增主键效率更高


相关文档

下一步:学习 MySQL 锁机制,理解不同索引类型对加锁的影响。

Last updated