MySQL 索引完全指南
一、索引的本质
索引是 MySQL 中帮助高效获取数据的有序数据结构。
数据库在原始数据外,会维护一套满足特定查找算法的数据结构(如 B+Tree),该结构通过指针关联原始数据,可基于算法快速定位数据,从而避免全表扫描,提升查询效率。
二、索引的优缺点
索引是 "双刃剑",需在查询效率与存储 / 更新成本间平衡:
维度 | 具体说明 |
---|---|
优点 | 1. 降低 IO 成本:减少数据检索时的磁盘读取次数,快速定位数据;2. 降低 CPU 成本:通过索引列的有序性,避免对原始数据排序,直接利用索引排序结果。 |
缺点 | 1. 占用存储空间:索引列需单独存储,一张表的索引越多,占用磁盘空间越大;2. 降低更新速度:INSERT/UPDATE/DELETE 时,需同步维护索引结构(如 B+Tree 调整),额外增加操作耗时。 |
三、MySQL 支持的索引结构
不同索引结构适用于不同场景,主流存储引擎(InnoDB/MyISAM/Memory)对结构的支持存在差异。
1. 常见索引结构对比
索引结构 | 核心原理 | 适用场景 | 优缺点总结 |
---|---|---|---|
B+Tree 索引 | 多路平衡查找树,叶子节点存储数据(或主键),且叶子节点形成有序链表 | 绝大多数查询场景(单值、范围、排序) | ✅ 支持范围查询 / 排序;✅ 层级少、效率高;❌ 需维护树结构 |
Hash 索引 | 基于哈希算法,将键值映射到哈希表槽位,冲突时用链表解决 | 仅精确匹配查询(=、IN) | ✅ 单次检索效率极高;❌ 不支持范围查询 / 排序 |
R-Tree 索引 | 空间索引,专为地理空间数据(如经纬度)设计 | 地理空间数据查询(如 "附近的地点") | ✅ 支持空间范围查询;❌ 仅 MyISAM 支持,使用极少 |
Full-Text 索引 | 基于倒排索引,将文本拆分为关键词,快速匹配包含关键词的文档 | 文本内容检索(如文章、评论) | ✅ 支持模糊文本查询;❌ 仅特定引擎 / 版本支持(InnoDB 5.6+) |
2. 各存储引擎对索引结构的支持
索引结构 | InnoDB | MyISAM | Memory |
---|---|---|---|
B+Tree 索引 | 支持(默认) | 支持 | 支持 |
Hash 索引 | 不支持(但有自适应 Hash,自动构建) | 不支持 | 支持(默认) |
R-Tree 索引 | 不支持 | 支持 | 不支持 |
Full-Text 索引 | 5.6 版本后支持 | 支持 | 不支持 |
四、核心索引结构深度解析(B-Tree vs B+Tree vs Hash)
1. 从二叉树到 B-Tree:解决 "层级过深" 问题
- 二叉树:每个节点最多 2 个子节点,数据量增大时会退化为 "链表"(如有序数据插入),层级极深,IO 次数暴增。
- 红黑树:通过 "红黑规则" 保持树的平衡,减少层级,但数据量超千万时,层级仍会达到 20+,检索效率不足。
- B-Tree(多路平衡查找树) :
允许每个节点存储多个键值和指针(如 5 阶 B-Tree 可存 4 个键值、5 个指针),大幅减少树的高度。
特点:非叶子节点和叶子节点均存储数据,所有叶子节点在同一层级。
2. B+Tree:MySQL 索引的 "最终选择"
B+Tree 是 B-Tree 的优化版,也是 InnoDB/MyISAM 的默认索引结构,MySQL 对其进一步优化(增加相邻叶子节点的链表指针) 。
(1)B+Tree 与 B-Tree 的核心区别
对比项 | B-Tree | B+Tree(MySQL 优化版) |
---|---|---|
数据存储位置 | 非叶子节点、叶子节点均存数据 | 仅叶子节点存数据,非叶子节点只存键值 + 指针 |
叶子节点关联 | 无关联 | 叶子节点形成双向链表(MySQL 优化) |
范围查询效率 | 需回溯节点,效率低 | 直接遍历叶子节点链表,效率高 |
(2)MySQL 优化后的 B+Tree 优势
- 层级更少:非叶子节点不存数据,单个节点可存更多键值,树高通常仅 2-3 层(见 "思考题 2" 计算),IO 次数极少。
- 范围查询快:叶子节点双向链表,可快速遍历 "某区间数据"(如 age between 20 and 30)。
- 排序友好:叶子节点有序,可直接利用索引完成排序(如 order by id)。
3. Hash 索引:仅适用于 "精确匹配"
- 原理:通过哈希函数将键值(如 id=10)换算为哈希值,映射到哈希表的某个槽位,槽位冲突时用链表存储相同哈希值的键值。
- 局限性:
-
- 不支持范围查询(如 id>10):哈希值无序,无法判断区间关系;
-
- 不支持排序(如 order by id):哈希值与键值无顺序关联;
-
- 冲突时效率下降:链表过长会导致检索时间从 "O (1)" 变为 "O (n)"。
五、索引的分类
1. 按 "功能用途" 分类
分类 | 含义 | 特点 | 关键字 / 创建方式 |
---|---|---|---|
主键索引 | 针对表的主键创建的索引 | 默认自动创建,一张表仅 1 个 | PRIMARY(如 id int PRIMARY KEY) |
唯一索引 | 确保索引列的值唯一(允许 NULL,但 NULL 仅出现一次) | 一张表可多个 | UNIQUE(如 name varchar(20) UNIQUE) |
常规索引 | 无特殊约束,仅用于快速定位数据 | 一张表可多个 | 无关键字(如 INDEX idx_age (age)) |
全文索引 | 对文本内容(如 text 类型)建立倒排索引,支持关键词匹配 | 一张表可多个,仅支持特定引擎 / 字段 | FULLTEXT(如 FULLTEXT idx_content (content)) |
2. InnoDB 特有的 "存储形式" 分类(核心!)
InnoDB 基于 "聚集索引" 组织数据,这是理解 InnoDB 索引的关键。
分类 | 含义 | 特点 | 叶子节点存储内容 |
---|---|---|---|
聚集索引 | 数据与索引 "聚在一起",索引结构即数据存储结构 | 必须有且仅 1 个,决定数据物理存储顺序 | 完整的行数据 |
二级索引 | 数据与索引分离,索引仅关联主键,需通过主键回查数据("回表") | 可多个,不影响数据物理顺序 | 对应的主键值 |
聚集索引的选取规则(优先级从高到低):
- 若表有主键,则主键索引即为聚集索引;
- 若无主键,选择第一个非空的唯一索引作为聚集索引;
- 若既无主键也无合适唯一索引,InnoDB 自动生成隐藏的 rowid 作为聚集索引。
六、典型问题与思考题解析
1. 面试题:为什么 InnoDB 选择 B+Tree 作为索引结构?
答:核心原因是 B+Tree 平衡了 "查询效率、范围支持、排序需求",具体对比如下:
- 对比二叉树 / 红黑树:B+Tree 层级更少(通常 2-3 层),减少磁盘 IO 次数,适合大数据量;
- 对比B-Tree:B+Tree 仅叶子节点存数据,非叶子节点存更多键值,层级更浅;且叶子节点链表支持高效范围查询;
- 对比Hash 索引:B+Tree 支持范围查询(如 between)和排序(如 order by),而 Hash 索引不支持。
2. 思考题 1:两条 SQL 执行效率对比
sql
select * from user where id = 10; -- ①
select * from user where name = 'Arm'; -- ②(name 有二级索引)
答:① 执行效率更高,原因如下:
- id 是主键,对应聚集索引,叶子节点直接存储行数据,仅需 1 次 B+Tree 检索即可获取完整数据;
- name 是二级索引,叶子节点仅存储主键 id,需先通过二级索引找到 id,再通过聚集索引查行数据(即 "回表"),多 1 次检索步骤。
3. 思考题 2:InnoDB 主键索引的 B+Tree 高度为多少?
答:基于 InnoDB 页大小(默认 16KB)计算,假设条件:
- 行数据大小:1KB / 行(则 1 页可存 16 行);
- 主键类型:bigint(8 字节);
- B+Tree 节点指针:6 字节。
计算过程:
- 单个非叶子节点能存多少个键值(n) ?
非叶子节点存储 "键值(8 字节)+ 指针(6 字节)",且最后一个指针额外占用 6 字节,公式为:
n*8 + (n+1)6 ≤ 161024(16KB=16384 字节)
解得 n≈1170(即单个非叶子节点可存 1170 个键值,1171 个指针)。
- 不同高度的存储容量:
-
- 高度 = 1:仅 1 个叶子节点,存储 16 行数据;
-
- 高度 = 2:1 个非叶子节点(1171 个指针)+ 1171 个叶子节点,总容量 = 1171*16≈1.8 万行;
-
- 高度 = 3:1 个顶层非叶子节点 + 1171 个中层非叶子节点 + 11711171 个叶子节点,总容量 = 11711171*16≈2200 万行;
-
- 高度 = 4:总容量≈2200 万 * 1171≈260 亿行(远超绝大多数业务场景)。
结论:InnoDB 主键索引的 B+Tree 高度通常为 2-3 层,即使千万级数据,也仅需 2-3 次磁盘 IO 即可定位数据。