Skip to content

Commit 1ea9254

Browse files
committed
[docs update]完善MySQL索引内容
1 parent 0c0efe5 commit 1ea9254

File tree

1 file changed

+88
-9
lines changed

1 file changed

+88
-9
lines changed

docs/database/mysql/mysql-index.md

+88-9
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,9 @@ MySQL 8.x 中实现的索引新特性:
174174

175175
## 二级索引
176176

177-
**二级索引(Secondary Index)又称为辅助索引,是因为二级索引的叶子节点存储的数据是主键。也就是说,通过二级索引,可以定位主键的位置。**
177+
二级索引(Secondary Index)的叶子节点存储的数据是主键的值,也就是说,通过二级索引可以定位主键的位置,二级索引又称为辅助索引/非主键索引。
178178

179-
唯一索引,普通索引,前缀索引等索引属于二级索引
179+
唯一索引,普通索引,前缀索引等索引都属于二级索引
180180

181181
PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
182182

@@ -195,7 +195,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
195195

196196
#### 聚簇索引介绍
197197

198-
**聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。**
198+
聚簇索引(Clustered Index)即索引结构和数据一起存放的索引,并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
199199

200200
在 MySQL 中,InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
201201

@@ -215,7 +215,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
215215

216216
#### 非聚簇索引介绍
217217

218-
**非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。**
218+
非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
219219

220220
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
221221

@@ -262,7 +262,9 @@ SELECT id FROM table WHERE id=1;
262262

263263
### 覆盖索引
264264

265-
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 **覆盖索引(Covering Index)** 。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。而覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
265+
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 **覆盖索引(Covering Index)**
266+
267+
在 InnoDB 存储引擎中,非主键索引的叶子节点包含的是主键的值。这意味着,当使用非主键索引进行查询时,数据库会先找到对应的主键值,然后再通过主键索引来定位和检索完整的行数据。这个过程被称为“回表”。
266268

267269
**覆盖索引即需要查询的字段正好是索引的字段,那么直接根据该索引,就可以查到数据了,而无需回表查询。**
268270

@@ -313,7 +315,8 @@ CALL BatchinsertDataToCusOder(1, 1000000); # 插入100w+的随机数据
313315
为了能够对这 100w 数据按照 `score` 进行排序,我们需要执行下面的 SQL 语句。
314316

315317
```sql
316-
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;#降序排序
318+
#降序排序
319+
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
317320
```
318321

319322
使用 `EXPLAIN` 命令分析这条 SQL 语句,通过 `Extra` 这一列的 `Using filesort` ,我们发现是没有用到覆盖索引的。
@@ -348,13 +351,89 @@ ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);
348351

349352
### 最左前缀匹配原则
350353

351-
最左前缀匹配原则指的是,在使用联合索引时,**MySQL** 会根据联合索引中的字段顺序,从左到右依次到查询条件中去匹配,如果查询条件中存在与联合索引中最左侧字段相匹配的字段,则就会使用该字段过滤一批数据,直至联合索引中全部字段匹配完成,或者在执行过程中遇到范围查询(如 **`>`****`<`** )才会停止匹配。对于 **`>=`****`<=`****`BETWEEN`****`like`** 前缀匹配的范围查询,并不会停止匹配。所以,我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
354+
最左前缀匹配原则指的是在使用联合索引时,MySQL 会根据索引中的字段顺序,从左到右依次匹配查询条件中的字段。如果查询条件与索引中的最左侧字段相匹配,那么 MySQL 就会使用索引来过滤数据,这样可以提高查询效率。
355+
356+
最左匹配原则会一直向右匹配,直到遇到范围查询(如 >、<)为止。对于 >=、<=、BETWEEN 以及前缀匹配 LIKE 的范围查询,不会停止匹配(相关阅读:[联合索引的最左匹配原则全网都在说的一个错误结论](https://mp.weixin.qq.com/s/8qemhRg5MgXs1So5YCv0fQ))。
357+
358+
假设有一个联合索引`(column1, column2, column3)`,其从左到右的所有前缀为`(column1)``(column1, column2)``(column1, column2, column3)`(创建 1 个联合索引相当于创建了 3 个索引),包含这些列的所有查询都会走索引而不会全表扫描。
359+
360+
我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
361+
362+
我们这里简单演示一下最左前缀匹配的效果。
363+
364+
1、创建一个名为 `student` 的表,这张表只有 `id``name``class`这 3 个字段。
365+
366+
```sql
367+
CREATE TABLE `student` (
368+
`id` int NOT NULL,
369+
`name` varchar(100) DEFAULT NULL,
370+
`class` varchar(100) DEFAULT NULL,
371+
PRIMARY KEY (`id`),
372+
KEY `name_class_idx` (`name`,`class`)
373+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
374+
```
375+
376+
2、下面我们分别测试三条不同的 SQL 语句。
377+
378+
![](https://oss.javaguide.cn/github/javaguide/database/mysql/leftmost-prefix-matching-rule.png)
379+
380+
```sql
381+
# 可以命中索引
382+
SELECT * FROM student WHERE name = 'Anne Henry';
383+
EXPLAIN SELECT * FROM student WHERE name = 'Anne Henry' AND class = 'lIrm08RYVk';
384+
# 无法命中索引
385+
SELECT * FROM student WHERE class = 'lIrm08RYVk';
386+
```
352387

353-
相关阅读:[联合索引的最左匹配原则全网都在说的一个错误结论](https://mp.weixin.qq.com/s/8qemhRg5MgXs1So5YCv0fQ)
388+
MySQL 8.0.13 版本引入了索引跳跃扫描(Index Skip Scan,简称 ISS),它可以在某些索引查询场景下提高查询效率。在没有 ISS 之前,不满足最左前缀匹配原则的联合索引查询中会执行全表扫描。而 ISS 允许 MySQL 在某些情况下避免全表扫描,即使查询条件不符合最左前缀。不过,这个功能比较鸡肋, 和 Oracle 中的没法比,MySQL 8.0.31 还报告了一个 bug:[Bug #109145 Using index for skip scan cause incorrect result](https://bugs.mysql.com/bug.php?id=109145)(后续版本已经修复)。个人建议知道有这个东西就好,不需要深究,实际项目也不一定能用上
354389

355390
## 索引下推
356391

357-
**索引下推(Index Condition Pushdown)****MySQL 5.6** 版本中提供的一项索引优化功能,可以在非聚簇索引遍历过程中,对索引中包含的字段先做判断,过滤掉不符合条件的记录,减少回表次数。
392+
**索引下推(Index Condition Pushdown,简称 ICP)****MySQL 5.6** 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 `WHERE`字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
393+
394+
假设我们有一个名为 `usr` 的表,其中包含 `id`, `name`, 和 `age` 3 个字段,`name` 字段上创建了索引。
395+
396+
```sql
397+
#查询名字以"Aoki"开头且年龄为30岁的用户
398+
EXPLAIN SELECT * FROM usr
399+
WHERE name LIKE 'Aoki%' AND age = 30;
400+
```
401+
402+
- 没有索引下推之前,即使 `name` 字段建立的索引可以帮助我们快速定位到了以“张”开头的用户,但我们仍然需要对每一个找到的以“张”开头的用户进行回表操作,获取完整的用户数据,再判断 `age` 字段是否等于 30。
403+
- 有了索引下推之后,存储引擎会在使用 `name` 索引查找以"张"开头的记录时,同时检查 `age` 字段是否等于 30。这样,只有同时满足 `name``age` 条件的记录才会被返回,减少了回表次数。
404+
405+
![](https://oss.javaguide.cn/github/javaguide/database/mysql/index-condition-pushdown.png)
406+
407+
再来讲讲索引下推的具体原理,先看下面这张 MySQL 简要架构图。
408+
409+
![](https://oss.javaguide.cn/javaguide/13526879-3037b144ed09eb88.png)
410+
411+
MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处理查询解析、分析、优化、缓存以及与客户端的交互等操作,而存储引擎层负责数据的存储和读取,MySQL 支持 InnoDB、MyISAM、Memory 等多种存储引擎。
412+
413+
索引下推的**下推**其实就是指将部分上层(Server 层)负责的事情,交给了下层(存储引擎层)去处理。
414+
415+
我们这里结合索引下推原理再对上面提到的例子进行解释。
416+
417+
没有索引下推之前:
418+
419+
- 存储引擎层先根据 `name` 索引字段找到所有以“张”开头用户的主键 ID,然后二次回表查询,获取完整的用户数据。
420+
- 存储引擎层把所有以“张”开头的用户数据全部交给 Server 层,Server 层根据`age` 字段是否等于 30 这一条件再进一步做筛选。
421+
422+
有了索引下推之后:
423+
424+
- 存储引擎层先根据 `name` 索引字段找到所有以“张”开头的用户,然后直接判断`age` 字段是否等于 30,筛选出符合条件的 主键 ID。
425+
- 二次回表查询,根据符合条件的主键 ID 去获取完整的用户数据,
426+
- 存储引擎层把符合条件的用户数据全部交给 Server 层。
427+
428+
可以看出,**除了可以减少回表次数之外,索引下推还可以减少存储引擎层和 Server 层的数据传输量。**
429+
430+
最后,总结一下索引下推应用范围:
431+
432+
1. 适用于 InnoDB 引擎和 MyISAM 引擎的查询。
433+
2. 适用于执行计划是 range, ref, eq_ref, ref_or_null 的范围查询。
434+
3. 对于 InnoDB 表,仅用于非聚簇索引。索引下推的目标是减少全行读取次数,从而减少 I/O 操作。对于 InnoDB 聚集索引,完整的记录已经读入 InnoDB 缓冲区。在这种情况下使用索引下推 不会减少 I/O。
435+
4. 子查询不能使用索引下推,因为子查询通常会创建临时表来处理结果,而这些临时表是没有索引的。
436+
5. 存储过程不能使用索引下推,因为存储引擎无法调用存储函数。
358437

359438
## 正确使用索引的一些建议
360439

0 commit comments

Comments
 (0)