在 MySQL 中,InnoDB 是最常用的存储引擎,它支持事务、行级锁和外键约束等功能,而索引则是提升数据库查询性能的关键。在 InnoDB 存储引擎中,索引不仅仅是提高查询速度的工具,还是数据库的核心组成部分之一。本文将详细介绍 InnoDB 存储引擎的索引结构、索引种类、索引优化技巧以及索引失效等方面的知识。
1. InnoDB 索引的结构
在 InnoDB 存储引擎中,索引主要分为两种类型:聚集索引(Clustered Index)和非聚集索引(Non-clustered Index)。
1.1 聚集索引(Clustered Index)
- 聚集索引的叶子节点存储的是数据行本身。换句话说,数据行的实际数据存储在索引结构中,索引的顺序就是数据的物理存储顺序。
- 每个表只能有一个聚集索引,通常是主键索引。
- 聚集索引通过将表的数据按索引顺序存储,从而提高了对主键字段查询的效率。
1.2 非聚集索引(Non-clustered Index)
- 非聚集索引的叶子节点并不存储数据行本身,而是存储数据行的指针(通常是聚集索引的主键值)。因此,查询时需要通过指针再次访问数据行。
- 一个表可以有多个非聚集索引。
1.3 InnoDB 索引的 B+ 树结构
InnoDB 存储引擎中的聚集索引和非聚集索引通常都是基于 B+ 树(自平衡的树状数据结构)来实现的。B+ 树有以下特点:
- 平衡性:所有叶子节点都位于同一层,因此查询的时间复杂度是 O(log N)。
- 有序性:叶子节点之间按照大小顺序排列,可以进行范围查询。
- 高效的插入和删除操作:通过自平衡机制,插入和删除操作能保持树的平衡。
2. InnoDB 索引的类别
InnoDB 支持以下几种类型的索引:
2.1 主键索引(Primary Key Index)
- 主键索引是一种特殊的聚集索引,用来确保表中每一行的唯一性。InnoDB 表默认使用主键作为聚集索引。
- 如果没有显式定义主键,InnoDB 会自动选定一个唯一索引作为主键。
2.2 唯一索引(Unique Index)
- 唯一索引保证索引列的值是唯一的,但允许 NULL 值存在(多个 NULL 值也被视为不同的值)。
- 唯一索引可以用于加速查询。
2.3 普通索引(Index)
- 普通索引是最常见的索引类型,它没有唯一性约束,仅用于提高查询效率。
- 可以用于加速查询,但不保证数据唯一性。
2.4 全文索引(Fulltext Index)
- 全文索引是专门用于对文本字段进行全文检索的索引类型。通常用于查找包含某个关键词的文本数据。
- 只适用于 CHAR、VARCHAR 和 TEXT 类型的列。
2.5 空间索引(Spatial Index)
- 空间索引主要用于对空间数据类型(如
GEOMETRY
)进行查询优化。它使用的是 R 树(Region Tree)而不是 B+ 树。
3. 索引的最左前缀原则
InnoDB 索引遵循 最左前缀原则。也就是说,当你使用复合索引(由多个列组成的索引)时,索引的查询可以使用到最左侧的一部分索引。
举个例子:
假设有一个复合索引 (a, b, c)
,那么查询时,可以利用以下的前缀索引:
(a)
:只使用列a
,是有效的。(a, b)
:使用列a
和列b
,是有效的。(a, b, c)
:使用列a
、b
和c
,是有效的。
但如果你只使用 (b)
或 (c)
作为查询条件,MySQL 将无法使用这个复合索引,因为它没有按照最左前缀的顺序来查询。
4. 索引覆盖(Covering Index)
索引覆盖是指查询中涉及到的所有列都包含在索引中,从而避免了对表数据的访问。换句话说,查询的数据完全通过索引获取,无需回表操作。
举个例子:
假设有一个复合索引 (a, b, c)
,并且你执行了如下查询:
SELECT a, b FROM table WHERE a = 1;
如果索引 (a, b, c)
已经包含了查询所需的列 a
和 b
,那么 MySQL 可以直接从索引中获取数据,而不需要回到表中去查询。
索引覆盖的好处是能显著提高查询性能,尤其是在大数据量的表中。
5. 索引下推(Index Condition Pushdown,ICP)
索引下推是一种优化技术,它能够将查询条件推送到存储引擎的索引扫描阶段,而不是等到读取数据行时再进行过滤。这样可以减少需要读取的数据行数量,提升查询效率。
- 在执行查询时,MySQL 会尽可能地将条件应用到索引扫描的阶段,而不是仅仅依赖于后续的行级过滤。
例如,
SELECT * FROM Employees WHERE age > 30 AND salary like '%5000'
其中联合索引(age、salary);
没有索引下推时,执行这条语句的流程:
-
1、存储引擎使用联合索引查出age>30的二级索引数据(叶子节点中有age、salary、主键);
-
2、拿到主键回表,到聚簇索引中拿到完整记录;
-
3、将所有的完整记录返回到server层(服务器层),再进行salary的模糊查询。
开启索引下推后,执行流程:
-
1、存储引擎使用联合索引查出age>30的二级索引数据(叶子节点中有age、salary、主键);
-
2、直接在二级索引数据中对salary进行模糊查询。
可以看出索引下推之后减少了回表的次数,从而降低了查询的时间。
6. 索引合并(Index Merge)
索引合并是 MySQL 在某些情况下使用的一个优化技术。当查询条件涉及多个索引时,MySQL 会尝试将多个索引的结果合并起来,从而加快查询速度。
举个例子:
假设有两个索引:idx_a
和 idx_b
,查询条件是:
SELECT * FROM table WHERE a = 1 OR b = 2;
MySQL 可以使用索引合并策略,首先分别从 idx_a
和 idx_b
中找到符合条件的记录,然后将它们合并,最后返回结果。
7. 索引失效的情况
索引并不是总能在所有情况下发挥作用。在以下情况下,索引可能会失效:
7.1 使用了不等于操作符(!=
、<>
)
SELECT * FROM table WHERE a != 1;
索引无法有效利用,因为不等于操作会导致扫描整个数据集。
7.2 使用了 OR
连接多个条件
SELECT * FROM table WHERE a = 1 OR b = 2;
当查询条件中包含多个列的 OR
时,索引可能无法有效地优化查询,尤其是当 OR
连接的列没有单独建立索引时。
7.3 使用函数
SELECT * FROM table WHERE YEAR(date_column) = 2023;
在where语句中使用函数(如 YEAR()
)会导致索引失效,因为索引是基于列值进行查找的,函数的使用会改变查询模式。
7.4 LIKE
前缀不匹配
SELECT * FROM table WHERE name LIKE '%abc';
如果 LIKE
查询以通配符 %
开头,索引通常无法被使用,因为 MySQL 无法利用前缀进行快速查找。
8. 如何查看索引
可以通过以下方式查看 MySQL 表的索引信息:
SHOW INDEX FROM table_name;
该语句将列出表 table_name
中所有的索引,包括索引的名称、类型、涉及的列等信息。
也可以使用explain关键字查看一条SQL的执行计划:
EXPLAIN SELECT * FROM users WHERE age > 30;
结果如下:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
---|
1 | SIMPLE | users | range | idx_age | idx_age | 4 | NULL | 2 | Using where |
这个执行计划的解释为:
- id: 查询的唯一标识。这里的
1
表示这是一个简单查询。 - select_type: 查询的类型。
SIMPLE
表示这是一个简单的查询(没有子查询)。 - table: 表名。这里查询的是
users
表。 - type: 这里的
range
表示 MySQL 使用了范围扫描(age > 30
是一个范围条件),这是比ALL
更高效的扫描类型。 - possible_keys: MySQL 能够使用的索引。这里没有索引可用(
NULL
)。 - key: 使用了
idx_age
索引,意味着查询能够利用索引进行更高效的检索。 - key_len: 使用的索引的长度。
- ref: 如果使用了索引,它将显示与哪个列比较。
- rows: 预估扫描了 2 行,MySQL 使用了索引来减少扫描的行数。
- Extra: 附加信息。
Using where
表示在扫描每一行时,MySQL 使用了WHERE
子句的条件进行过滤。
9. 索引优化建议
- 选择合适的索引类型:根据查询的特点选择合适的索引类型。如果查询需要精确匹配,可以选择唯一索引;如果查询涉及文本搜索,可以选择全文索引。
- 避免过度索引:索引虽然提高查询效率,但会增加写操作的开销。需要在查询性能和写操作性能之间做出平衡。
- 创建复合索引:对于多个列的查询,可以考虑创建复合索引,而不是对每个单独列都创建索引。
- 避免索引失效的情况:尽量避免在查询中使用
LIKE
、OR
、!=
等会导致索引失效的操作。 - 定期分析和优化索引:通过
EXPLAIN
语句来分析查询的执行计划,检查索引的使用情况,并根据结果调整索引设计。