# MySQL 的索引类型有哪些?
Table of Contents
MySQL 的索引类型有哪些?
MySQL 索引类型详解
【核心定义】
MySQL 索引是一种帮助存储引擎高效获取数据的排好序的数据结构,它通过减少磁盘 I/O 次数来加速数据检索。
【关键要点】
-
按数据结构分类
- B+Tree 索引:MySQL 默认且最常用的索引类型,适用于全值匹配、范围查询和排序操作。
- Hash 索引:基于哈希表实现,仅支持等值查询(=, IN),不支持范围查询和排序,Memory 引擎默认使用。
- R-Tree(空间索引):用于地理空间数据类型(如 GEOMETRY),支持空间关系查询。
- Full-Text(全文索引):基于倒排索引实现,用于全文搜索,支持 MATCH AGAINST 操作。
-
按物理存储分类
- 聚簇索引(Clustered Index):InnoDB 中,表数据文件本身就是按 B+Tree 组织的一个索引结构,叶子节点存储完整的行数据。一张表只有一个聚簇索引。
- 非聚簇索引(Secondary Index / 辅助索引):叶子节点存储的是主键值(InnoDB)或行数据的物理地址(MyISAM)。查询时需要回表。
-
按字段特性与数量分类
- 主键索引(PRIMARY KEY):特殊的唯一索引,不允许 NULL 值。InnoDB 中它就是聚簇索引。
- 唯一索引(UNIQUE KEY):保证索引列的值唯一,允许 NULL 值(但最多一个 NULL)。
- 普通索引(INDEX / KEY):最基本的索引,无任何约束。
- 联合索引(复合索引):在多个列上建立的索引。遵循最左前缀匹配原则。
-
按数据存储方式分类(InnoDB 特有)
- 聚集索引:即聚簇索引。
- 二级索引:即非聚簇索引,包括唯一索引、普通索引、联合索引。
【深度推导/细节】
1. B+Tree 为何是 MySQL 索引的默认选择?
- 对比 B-Tree:B+Tree 非叶子节点只存键值,不存数据,因此单次 I/O 能加载更多索引键,树的高度更低。所有数据都存储在叶子节点,且叶子节点间有双向链表连接,范围查询和顺序访问效率极高。
- 对比 Hash:Hash 索引虽等值查询 O(1),但无法支持
>、<、BETWEEN等范围查询,也无法利用索引排序。且哈希冲突处理(拉链法)在数据量大时可能退化。 - 磁盘 I/O 优化:B+Tree 的扇出性高,3-4 层即可存储亿级数据,将随机 I/O 转为顺序 I/O(通过叶子节点链表)。
2. 联合索引的最左前缀原则与索引下推(ICP)
- 最左前缀原则:对于索引
(a, b, c),查询条件必须包含a才能使用该索引。有效组合:(a),(a, b),(a, b, c)。 - 索引下推(ICP, MySQL 5.6+):在联合索引查询时,存储引擎层会先根据索引中包含的字段进行过滤(即使该字段不能用于索引查找),再将结果返回给 Server 层,减少回表次数。
- 示例:
WHERE a = 1 AND b LIKE ‘%abc’,在 ICP 开启后,引擎会在索引中同时判断a=1和b LIKE ‘%abc’,而不是只判断a=1就回表。
- 示例:
3. 覆盖索引与回表查询
- 覆盖索引:查询的字段全部包含在某个索引中,引擎无需回表即可返回结果,性能极高。
- 优化策略:高频查询尽量设计为覆盖索引,或使用联合索引“覆盖”更多字段。
- 回表查询:当使用二级索引查询时,若所需字段不在索引中,需要根据叶子节点存储的主键 ID 回到聚簇索引中查找完整行数据。这是主要的性能瓶颈之一。
【关联/对比】
| 特性 | InnoDB 索引 | MyISAM 索引 |
|---|---|---|
| 索引类型 | 聚簇索引 + 二级索引 | 非聚簇索引(索引和数据文件分离) |
| 文件存储 | .ibd 文件存储索引和数据 | .MYI 文件存索引,.MYD 文件存数据 |
| 叶子节点内容 | 聚簇索引存行数据,二级索引存主键值 | 存指向数据行的物理地址(偏移量) |
| 事务支持 | 支持 | 不支持 |
与 MongoDB 索引对比:MongoDB 默认使用 B-Tree,但也支持哈希、地理空间等索引。其“覆盖查询”概念与 MySQL 类似,但数据结构更灵活(文档模型)。
『面试官追问』
-
为什么 InnoDB 表必须有且只有一个聚簇索引?
- 因为数据本身只能按一种物理顺序存储。如果未定义主键,InnoDB 会选择一个唯一的非空索引代替,若没有,则会隐式创建一个 6 字节的
ROWID作为聚簇索引。
- 因为数据本身只能按一种物理顺序存储。如果未定义主键,InnoDB 会选择一个唯一的非空索引代替,若没有,则会隐式创建一个 6 字节的
-
Hash 索引在哪些场景下可能比 B+Tree 快?为什么不用它做默认索引?
- 场景:纯等值查询且数据离散度高、不涉及排序和范围查询时(如缓存表)。
- 原因:如前所述,其无法支持数据库最常用的范围查询和排序操作,且不支持部分索引键查询(联合索引必须全键匹配)。Memory 引擎支持。
-
什么是自适应哈希索引(AHI)?
- InnoDB 的内部优化机制。当监测到某些索引值被非常频繁地访问时,会在内存的 Buffer Pool 中为其建立一个哈希索引,从而将 B+Tree 的 O(log n) 查询优化为 O(1)。此过程对用户完全透明。
-
全文索引底层是怎么实现的?和 LIKE 模糊查询有什么区别?
- 实现:基于倒排索引。它会提取文本中的关键词(分词),并记录每个关键词在哪些文档中出现以及位置。
- 区别:
LIKE ‘%keyword%’会导致全表扫描,效率极低。全文索引通过分词和倒排列表能快速定位包含关键词的文档,且支持相关性评分和多种搜索模式(自然语言模式、布尔模式)。
-
索引是不是越多越好?
- 绝对不是。索引的代价:
- 空间代价:每个索引都是一棵 B+Tree,占用磁盘和内存。
- 时间代价:DML(增删改)操作需要维护所有索引树,降低写入性能。
- 优化器代价:索引过多会增加优化器选择执行计划的时间。
- 建议:通常单表索引数不建议超过 5 个,核心是覆盖高频且性能关键查询。
- 绝对不是。索引的代价:
【直击痛点:关键数字与设计】
-
为什么 B+Tree 通常 3-4 层就能存亿级数据? 假设一个页(Page)大小为 16KB,一个主键(如 BIGINT)为 8 字节,一个指针为 6 字节。那么一个非叶子节点页大约可存储:
16KB / (8B+6B) ≈ 1170个键值对。假设叶子节点一行数据 1KB,则一页可存约 16 行。- 计算:
- 第一层(根):1 页, 1170 个指针。
- 第二层:1170 页,每页 1170 个指针 → 共
1170 * 1170 ≈ 1.37M个指针。 - 第三层(叶子):
1.37M页,每页 16 行 → 总行数约1.37M * 16 ≈ 2190 万。 - 若为四层:
1170^3 * 16 ≈ 256 亿。因此,三层足以支撑千万级,四层支撑百亿级。
- 计算:
-
联合索引字段顺序如何设计?
- 区分度最高的列放左边(选择性高),能最快过滤数据。
- 考虑查询频率和查询类型,常作为查询条件的列优先。
- 考虑覆盖索引,将查询中需要返回的列加到索引末尾。
- 遵循最左前缀原则,避免设计出无法被使用的索引。
【逻辑复现:一次索引查询的旅程(以 InnoDB 为例)】
场景:SELECT * FROM users WHERE name = ‘Alice’;, name 字段上有普通二级索引。
Step 1: 索引查找
存储引擎从 name 索引的 B+Tree 根节点开始,利用二分查找逐层向下,直到在叶子节点找到 ‘Alice’ 对应的主键 ID(如 id=5)。
Step 2: 回表
引擎拿着这个主键 ID(id=5),回到**聚簇索引(主键索引)**的 B+Tree 中,同样从根开始查找,直到定位到存储了完整 users 表中 id=5 这一行数据的叶子页。
Step 3: 数据返回 引擎从该页中读取整行数据,返回给 Server 层。
Step 4: 若使用覆盖索引
如果查询是 SELECT id FROM users WHERE name = ‘Alice’;,由于 id 已经在 name 索引的叶子节点中(二级索引存主键值),引擎在 Step 1 后即可直接返回结果,跳过 Step 2 和 Step 3,性能大幅提升。
【版本差异】
- MySQL 5.6:引入了索引下推(ICP),显著优化了联合索引中范围查询后的字段过滤。
- MySQL 5.7:支持生成列(Generated Columns) 及其索引,可用于对 JSON 数据类型建立索引。
- MySQL 8.0:
- 支持降序索引(
DESC在索引定义中生效),优化ORDER BY a DESC, b DESC查询。 - 支持函数索引(如
CREATE INDEX idx ON t1 ((JSON_EXTRACT(col, ‘$.path’))))。 - 废弃了查询缓存(Query Cache),更强调索引和引擎层优化。
- 支持降序索引(
总结:选择正确的索引类型是数据库性能优化的基石。理解其底层数据结构(尤其是 B+Tree)、存储方式(聚簇/非聚簇)以及 MySQL 的版本特性,才能设计出高效的索引策略,在空间和时间成本间取得最佳平衡。