1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

Merge pull request #2627 from YJRY/main

文档内容修正
This commit is contained in:
Guide 2025-03-01 15:53:29 +08:00 committed by GitHub
commit 83f39a33b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 81 additions and 81 deletions

View File

@ -21,7 +21,7 @@ tag:
### 所有表必须使用 InnoDB 存储引擎
没有特殊要求(即 InnoDB 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎MySQL5.5 之前默认使用 Myisam5.6 以后默认的为 InnoDB
没有特殊要求(即 InnoDB 无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎MySQL5.5 之前默认使用 MyISAM5.6 以后默认的为 InnoDB
InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。

View File

@ -15,20 +15,20 @@ tag:
**索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。**
索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引的作用就相当于书的目录。打个比方:我们在查字典的时候,如果没有目录,那我们就只能一页一页地去找我们需要查的那个字,速度很慢;如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam都使用了 B+树作为索引结构。
索引底层数据结构存在很多种类型,常见的索引结构有B 树、 B+ 树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyISAM都使用了 B+ 树作为索引结构。
## 索引的优缺点
**优点**
**优点**
- 使用索引可以大大加快数据的检索速度(大大减少检索的数据量), 减少 IO 次数,这也是创建索引的最主要的原因。
- 使用索引可以大大加快数据的检索速度(大大减少检索的数据量)减少 IO 次数,这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
**缺点**
**缺点**
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 创建和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态地修改,这会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
但是,**使用索引一定能提高查询性能吗?**
@ -39,7 +39,7 @@ tag:
### Hash 表
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O1)。
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
**为何能够通过 key 快速取出 value 呢?** 原因在于 **哈希算法**(也叫散列算法)。通过哈希算法,我们可以快速找到 key 对应的 index找到了 index 也就找到了对应的 value。
@ -50,7 +50,7 @@ index = hash % array_size
![](https://oss.javaguide.cn/github/javaguide/database/mysql20210513092328171.png)
但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过JDK1.8 以后`HashMap`为了减少链表过长的时候搜索时间过长引入了红黑树。
但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过JDK1.8 以后`HashMap`为了提高链表过长时的搜索效率,引入了红黑树。
![](https://oss.javaguide.cn/github/javaguide/database/mysql20210513092224836.png)
@ -60,15 +60,15 @@ MySQL 的 InnoDB 存储引擎不直接支持常规的哈希索引但是Inn
既然哈希表这么快,**为什么 MySQL 没有使用其作为索引的数据结构呢?** 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。
试想一种情况:
试想一种情况
```java
SELECT * FROM tb1 WHERE id < 500;
```
在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗?这就是 Hash 最大的缺点了。
在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗这就是 Hash 最大的缺点了。
### 二叉查找树(BST)
### 二叉查找树BST
二叉查找树Binary Search Tree是一种基于二叉树的数据结构它具有以下特点
@ -76,7 +76,7 @@ SELECT * FROM tb1 WHERE id < 500;
2. 右子树所有节点的值均大于根节点的值。
3. 左右子树也分别为二叉查找树。
当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂退化为 ON
当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂退化为 O(N)
![斜树](https://oss.javaguide.cn/github/javaguide/cs-basics/data-structure/oblique-tree.png)
@ -92,7 +92,7 @@ AVL 树是计算机科学中最早被发明的自平衡二叉查找树,它的
AVL 树采用了旋转操作来保持平衡。主要有四种旋转操作LL 旋转、RR 旋转、LR 旋转和 RL 旋转。其中 LL 旋转和 RR 旋转分别用于处理左左和右右失衡,而 LR 旋转和 RL 旋转则用于处理左右和右左失衡。
由于 AVL 树需要频繁地进行旋转操作来保持平衡,因此会有较大的计算开销进而降低了数据库写操作的性能。并且, 在使用 AVL 树时,每个树节点仅存储一个数据,而每次进行磁盘 IO 时只能读取一个节点的数据,如果需要查询的数据分布在多个节点上,那么就需要进行多次磁盘 IO。 **磁盘 IO 是一项耗时的操作,在设计数据库索引时,我们需要优先考虑如何最大限度地减少磁盘 IO 操作的次数。**
由于 AVL 树需要频繁地进行旋转操作来保持平衡,因此会有较大的计算开销进而降低了数据库写操作的性能。并且, 在使用 AVL 树时,每个树节点仅存储一个数据,而每次进行磁盘 IO 时只能读取一个节点的数据,如果需要查询的数据分布在多个节点上,那么就需要进行多次磁盘 IO。**磁盘 IO 是一项耗时的操作,在设计数据库索引时,我们需要优先考虑如何最大限度地减少磁盘 IO 操作的次数。**
实际应用中AVL 树使用的并不多。
@ -112,26 +112,26 @@ AVL 树采用了旋转操作来保持平衡。主要有四种旋转操作LL
**红黑树的应用还是比较广泛的TreeMap、TreeSet 以及 JDK1.8 的 HashMap 底层都用到了红黑树。对于数据在内存中的这种情况来说,红黑树的表现是非常优异的。**
### B 树& B+树
### B 树& B+
B 树也称 B-树,全称为 **多路平衡查找树** B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 `Balanced` (平衡)的意思。
B 树也称 B- 树,全称为 **多路平衡查找树**B+ 树是 B 树的一种变体。B 树和 B+ 树中的 B 是 `Balanced`(平衡)的意思。
目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
**B 树& B+树两者有何异同呢?**
**B 树& B+ 树两者有何异同呢?**
- B 树的所有节点既存放键(key) 也存放数据(data),而 B+树只有叶子节点存放 key 和 data其他内节点只存放 key。
- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
- 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+树的范围查询,只需要对链表进行遍历即可。
- B 树的所有节点既存放键(key)也存放数据(data),而 B+ 树只有叶子节点存放 key 和 data其他内节点只存放 key。
- B 树的叶子节点都是独立的B+ 树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+ 树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
- 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+ 树的范围查询,只需要对链表进行遍历即可。
综上B+树与 B 树相比,具备更少的 IO 次数、更稳定的查询效率和更适于范围查询这些优势。
综上B+ 树与 B 树相比,具备更少的 IO 次数、更稳定的查询效率和更适于范围查询这些优势。
在 MySQL 中MyISAM 引擎和 InnoDB 引擎都是使用 B+Tree 作为索引结构但是两者的实现方式不太一样。下面的内容整理自《Java 工程师修炼之道》)
> MyISAM 引擎中B+Tree 叶节点的 data 域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree 搜索算法搜索索引,如果指定的 Key 存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“**非聚簇索引(非聚集索引)**”。
>
> InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM索引文件和数据文件是分离的其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“**聚簇索引(聚集索引)**”,而其余的索引都作为 **辅助索引** ,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
> InnoDB 引擎中,其数据文件本身就是索引文件。相比 MyISAM索引文件和数据文件是分离的其表数据文件本身就是按 B+Tree 组织的一个索引结构,树的叶节点 data 域保存了完整的数据记录。这个索引的 key 是数据表的主键,因此 InnoDB 表数据文件本身就是主索引。这被称为“**聚簇索引(聚集索引)**”,而其余的索引都作为 **辅助索引**,辅助索引的 data 域存储相应记录主键的值而不是地址,这也是和 MyISAM 不同的地方。在根据主索引搜索时,直接找到 key 所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
## 索引类型总结
@ -140,12 +140,12 @@ B 树也称 B-树,全称为 **多路平衡查找树** B+ 树是 B 树的一
- BTree 索引MySQL 里默认和最常用的索引类型。只有叶子节点存储 value非叶子节点只有指针和 key。存储引擎 MyISAM 和 InnoDB 实现 BTree 索引都是使用 B+Tree但二者实现方式不一样前面已经介绍了
- 哈希索引:类似键值对的形式,一次即可定位。
- RTree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR` `TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR``TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
按照底层存储方式角度划分:
- 聚簇索引聚集索引索引结构和数据一起存放的索引InnoDB 中的主键索引就属于聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分:
@ -154,7 +154,7 @@ B 树也称 B-树,全称为 **多路平衡查找树** B+ 树是 B 树的一
- 唯一索引:加速查询 + 列值唯一(可以有 NULL
- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR` `TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR``TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 前缀索引:对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
MySQL 8.x 中实现的索引新特性:
@ -163,7 +163,7 @@ MySQL 8.x 中实现的索引新特性:
- 降序索引:之前的版本就支持通过 desc 来指定索引为降序,但实际上创建的仍然是常规的升序索引。直到 MySQL 8.x 版本才开始真正支持降序索引。另外,在 MySQL 8.x 版本中,不再对 GROUP BY 语句进行隐式排序。
- 函数索引:从 MySQL 8.0.13 版本开始支持在索引中使用函数或者表达式的值,也就是在索引中可以包含函数或者表达式。
## 主键索引(Primary Key)
## 主键索引Primary Key
数据表的主键列使用的就是主键索引。
@ -177,16 +177,16 @@ MySQL 8.x 中实现的索引新特性:
二级索引Secondary Index的叶子节点存储的数据是主键的值也就是说通过二级索引可以定位主键的位置二级索引又称为辅助索引/非主键索引。
唯一索引,普通索引,前缀索引等索引都属于二级索引。
唯一索引、普通索引、前缀索引等索引都属于二级索引。
PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
PS不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
1. **唯一索引(Unique Key)**:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
2. **普通索引(Index)**:普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
3. **前缀索引(Prefix)**:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
4. **全文索引(Full Text)**:全文索引主要是为了检索大文本数据中的关键字的信息是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引5.6 之后 InnoDB 也支持了全文索引。
1. **唯一索引Unique Key** 唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
2. **普通索引Index** 普通索引的唯一作用就是为了快速查询数据。一张表允许创建多个普通索引,并允许数据重复和 NULL。
3. **前缀索引Prefix** 前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
4. **全文索引Full Text** 全文索引主要是为了检索大文本数据中的关键字的信息是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MyISAM 引擎支持全文索引5.6 之后 InnoDB 也支持了全文索引。
二级索引:
二级索引
![二级索引](https://oss.javaguide.cn/github/javaguide/open-source-project/no-cluster-index.png)
@ -198,48 +198,48 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
聚簇索引Clustered Index即索引结构和数据一起存放的索引并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
在 MySQL 中InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
在 MySQL 中InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引B+ 树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
#### 聚簇索引的优缺点
**优点**
**优点**
- **查询速度非常快**聚簇索引的查询速度非常的快,因为整个 B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 IO 操作。
- **对排序查找和范围查找优化**聚簇索引对于主键的排序查找和范围查找速度非常快。
- **查询速度非常快** 聚簇索引的查询速度非常的快,因为整个 B+ 树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。相比于非聚簇索引, 聚簇索引少了一次读取数据的 IO 操作。
- **对排序查找和范围查找优化** 聚簇索引对于主键的排序查找和范围查找速度非常快。
**缺点**
**缺点**
- **依赖于有序的数据**:因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
- **更新代价大**如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
- **依赖于有序的数据** 因为 B+ 树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
- **更新代价大** 如果对索引列的数据被修改时,那么对应的索引也将会被修改,而且聚簇索引的叶子节点还存放着数据,修改代价肯定是较大的,所以对于主键索引来说,主键一般都是不可被修改的。
### 非聚簇索引(非聚集索引)
#### 非聚簇索引介绍
非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引Non-Clustered Index即索引结构和数据分开存放的索引并不是一种单独的索引类型。二级索引辅助索引就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
#### 非聚簇索引的优缺点
**优点**
**优点**
更新代价比聚簇索引要小 。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的。
更新代价比聚簇索引要小。非聚簇索引的更新代价就没有聚簇索引那么大了,非聚簇索引的叶子节点是不存放数据的。
**缺点**
**缺点**
- **依赖于有序的数据**:跟聚簇索引一样,非聚簇索引也依赖于有序的数据
- **可能会二次查询(回表)**:这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
- **依赖于有序的数据** 跟聚簇索引一样,非聚簇索引也依赖于有序的数据
- **可能会二次查询(回表):** 这应该是非聚簇索引最大的缺点了。当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
这是 MySQL 的表的文件截图:
这是 MySQL 的表的文件截图
![MySQL 表的文件](https://oss.javaguide.cn/github/javaguide/database/mysql20210420165311654.png)
聚簇索引和非聚簇索引:
聚簇索引和非聚簇索引
![聚簇索引和非聚簇索引](https://oss.javaguide.cn/github/javaguide/database/mysql20210420165326946.png)
#### 非聚簇索引一定回表查询吗(覆盖索引)?
#### 非聚簇索引一定回表查询吗(覆盖索引)?
**非聚簇索引不一定回表查询。**
@ -251,7 +251,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
那么这个索引的 key 本身就是 name查到对应的 name 直接返回就行了,无需回表查询。
即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!**如果 SQL 查的就是主键呢?**
即使是 MyISAM 也是这样,虽然 MyISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!**如果 SQL 查的就是主键呢?**
```sql
SELECT id FROM table WHERE id=1;
@ -263,7 +263,7 @@ SELECT id FROM table WHERE id=1;
### 覆盖索引
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 **覆盖索引Covering Index**
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 **覆盖索引Covering Index**
在 InnoDB 存储引擎中,非主键索引的叶子节点包含的是主键的值。这意味着,当使用非主键索引进行查询时,数据库会先找到对应的主键值,然后再通过主键索引来定位和检索完整的行数据。这个过程被称为“回表”。
@ -276,7 +276,7 @@ SELECT id FROM table WHERE id=1;
我们这里简单演示一下覆盖索引的效果。
1、创建一个名为 `cus_order` 的表,来实际测试一下这种排序方式。为了测试方便, `cus_order` 这张表只有 `id``score``name`这 3 个字段。
1、创建一个名为 `cus_order` 的表,来实际测试一下这种排序方式。为了测试方便,`cus_order` 这张表只有 `id``score``name` 这 3 个字段。
```sql
CREATE TABLE `cus_order` (
@ -320,7 +320,7 @@ CALL BatchinsertDataToCusOder(1, 1000000); # 插入100w+的随机数据
SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
```
使用 `EXPLAIN` 命令分析这条 SQL 语句,通过 `Extra` 这一列的 `Using filesort` ,我们发现是没有用到覆盖索引的。
使用 `EXPLAIN` 命令分析这条 SQL 语句,通过 `Extra` 这一列的 `Using filesort`,我们发现是没有用到覆盖索引的。
![](https://oss.javaguide.cn/github/javaguide/mysql/not-using-covering-index-demo.png)
@ -336,7 +336,7 @@ ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);
![](https://oss.javaguide.cn/github/javaguide/mysql/using-covering-index-demo.png)
通过 `Extra` 这一列的 `Using index` ,说明这条 SQL 语句成功使用了覆盖索引。
通过 `Extra` 这一列的 `Using index`,说明这条 SQL 语句成功使用了覆盖索引。
关于 `EXPLAIN` 命令的详细介绍请看:[MySQL 执行计划分析](./mysql-query-execution-plan.md)这篇文章。
@ -356,13 +356,13 @@ ALTER TABLE `cus_order` ADD INDEX id_score_name(score, name);
最左匹配原则会一直向右匹配,直到遇到范围查询(如 >、<)为止。对于 >=、<=、BETWEEN 以及前缀匹配 LIKE 的范围查询,不会停止匹配(相关阅读:[联合索引的最左匹配原则全网都在说的一个错误结论](https://mp.weixin.qq.com/s/8qemhRg5MgXs1So5YCv0fQ))。
假设有一个联合索引`(column1, column2, column3)`,其从左到右的所有前缀为`(column1)``(column1, column2)``(column1, column2, column3)`(创建 1 个联合索引相当于创建了 3 个索引),包含这些列的所有查询都会走索引而不会全表扫描。
假设有一个联合索引 `(column1, column2, column3)`,其从左到右的所有前缀为 `(column1)``(column1, column2)``(column1, column2, column3)`(创建 1 个联合索引相当于创建了 3 个索引),包含这些列的所有查询都会走索引而不会全表扫描。
我们在使用联合索引时,可以将区分度高的字段放在最左边,这也可以过滤更多数据。
我们这里简单演示一下最左前缀匹配的效果。
1、创建一个名为 `student` 的表,这张表只有 `id``name``class`这 3 个字段。
1、创建一个名为 `student` 的表,这张表只有 `id``name``class` 这 3 个字段。
```sql
CREATE TABLE `student` (
@ -386,21 +386,21 @@ EXPLAIN SELECT * FROM student WHERE name = 'Anne Henry' AND class = 'lIrm08RYVk'
SELECT * FROM student WHERE class = 'lIrm08RYVk';
```
再来看一个常见的面试题:如果有索引 `联合索引abc`,查询 `a=1 AND c=1`会走索引么?`c=1` 呢?`b=1 AND c=1`呢?
再来看一个常见的面试题:如果有索引 `联合索引abc`,查询 `a=1 AND c=1` 会走索引么?`c=1` 呢?`b=1 AND c=1` 呢?
先不要往下看答案,给自己 3 分钟时间想一想。
1. 查询 `a=1 AND c=1`:根据最左前缀匹配原则,查询可以使用索引的前缀部分。因此,该查询仅在 `a=1` 上使用索引,然后对结果进行 `c=1` 的过滤。
2. 查询 `c=1` :由于查询中不包含最左列 `a`,根据最左前缀匹配原则,整个索引都无法被使用。
3. 查询`b=1 AND c=1`:和第二种一样的情况,整个索引都不会使用。
3. 查询 `b=1 AND c=1`:和第二种一样的情况,整个索引都不会使用。
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)(后续版本已经修复)。个人建议知道有这个东西就好,不需要深究,实际项目也不一定能用上。
## 索引下推
**索引下推Index Condition Pushdown简称 ICP** 是 **MySQL 5.6** 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 `WHERE`字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
**索引下推Index Condition Pushdown简称 ICP** 是 **MySQL 5.6** 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 `WHERE` 字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
假设我们有一个名为 `user` 的表,其中包含 `id`, `username`, `zipcode``birthdate` 4 个字段,创建了联合索引`(zipcode, birthdate)`
假设我们有一个名为 `user` 的表,其中包含 `id``username``zipcode` `birthdate` 4 个字段,创建了联合索引 `(zipcode, birthdate)`
```sql
CREATE TABLE `user` (
@ -417,7 +417,7 @@ SELECT * FROM user WHERE zipcode = '431200' AND MONTH(birthdate) = 3;
```
- 没有索引下推之前,即使 `zipcode` 字段利用索引可以帮助我们快速定位到 `zipcode = '431200'` 的用户,但我们仍然需要对每一个找到的用户进行回表操作,获取完整的用户数据,再去判断 `MONTH(birthdate) = 3`
- 有了索引下推之后,存储引擎会在使用`zipcode` 字段索引查找`zipcode = '431200'` 的用户时,同时判断`MONTH(birthdate) = 3`。这样,只有同时满足条件的记录才会被返回,减少了回表次数。
- 有了索引下推之后,存储引擎会在使用 `zipcode` 字段索引查找 `zipcode = '431200'` 的用户时,同时判断 `MONTH(birthdate) = 3`。这样,只有同时满足条件的记录才会被返回,减少了回表次数。
![](https://oss.javaguide.cn/github/javaguide/database/mysql/index-condition-pushdown.png)
@ -429,14 +429,14 @@ SELECT * FROM user WHERE zipcode = '431200' AND MONTH(birthdate) = 3;
MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处理查询解析、分析、优化、缓存以及与客户端的交互等操作而存储引擎层负责数据的存储和读取MySQL 支持 InnoDB、MyISAM、Memory 等多种存储引擎。
索引下推的**下推**其实就是指将部分上层Server 层)负责的事情,交给了下层(存储引擎层)去处理。
索引下推的 **下推** 其实就是指将部分上层Server 层)负责的事情,交给了下层(存储引擎层)去处理。
我们这里结合索引下推原理再对上面提到的例子进行解释。
没有索引下推之前:
- 存储引擎层先根据 `zipcode` 索引字段找到所有 `zipcode = '431200'` 的用户的主键 ID然后二次回表查询获取完整的用户数据
- 存储引擎层把所有 `zipcode = '431200'` 的用户数据全部交给 Server 层Server 层根据`MONTH(birthdate) = 3`这一条件再进一步做筛选。
- 存储引擎层把所有 `zipcode = '431200'` 的用户数据全部交给 Server 层Server 层根据 `MONTH(birthdate) = 3` 这一条件再进一步做筛选。
有了索引下推之后:
@ -449,8 +449,8 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
最后,总结一下索引下推应用范围:
1. 适用于 InnoDB 引擎和 MyISAM 引擎的查询。
2. 适用于执行计划是 range, ref, eq_ref, ref_or_null 的范围查询。
3. 对于 InnoDB 表,仅用于非聚簇索引。索引下推的目标是减少全行读取次数,从而减少 I/O 操作。对于 InnoDB 聚集索引,完整的记录已经读入 InnoDB 缓冲区。在这种情况下使用索引下推 不会减少 I/O。
2. 适用于执行计划是 range、ref、eq_ref、ref_or_null 的范围查询。
3. 对于 InnoDB 表,仅用于非聚簇索引。索引下推的目标是减少全行读取次数,从而减少 I/O 操作。对于 InnoDB 聚集索引,完整的记录已经读入 InnoDB 缓冲区。在这种情况下使用索引下推不会减少 I/O。
4. 子查询不能使用索引下推,因为子查询通常会创建临时表来处理结果,而这些临时表是没有索引的。
5. 存储过程不能使用索引下推,因为存储引擎无法调用存储函数。
@ -458,11 +458,11 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
### 选择合适的字段创建索引
- **不为 NULL 的字段**索引字段的数据应该尽量不为 NULL因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
- **被频繁查询的字段**我们创建索引的字段应该是查询操作非常频繁的字段。
- **被作为条件查询的字段**被作为 WHERE 条件查询的字段,应该被考虑建立索引。
- **频繁需要排序的字段**索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
- **被经常频繁用于连接的字段**经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
- **不为 NULL 的字段** 索引字段的数据应该尽量不为 NULL因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL建议使用 0、1、true、false 这样语义较为清晰的短值或短字符作为替代。
- **被频繁查询的字段** 我们创建索引的字段应该是查询操作非常频繁的字段。
- **被作为条件查询的字段** 被作为 WHERE 条件查询的字段,应该被考虑建立索引。
- **频繁需要排序的字段** 索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
- **被经常频繁用于连接的字段** 经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
### 被频繁更新的字段应该慎重建立索引
@ -470,7 +470,7 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
### 限制每张表上的索引数量
索引并不是越多越好,建议单张表索引不超过 5 个!索引可以提高效率同样可以降低效率。
索引并不是越多越好,建议单张表索引不超过 5 个!索引可以提高效率同样可以降低效率。
索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。
@ -478,11 +478,11 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
### 尽可能的考虑建立联合索引而不是单列索引
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+ 树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
### 注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如name,city name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city)和(name)这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的。在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
### 字符串类型的字段使用前缀索引代替普通索引
@ -492,13 +492,13 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
- ~~使用 `SELECT *` 进行查询;~~ `SELECT *` 不会直接导致索引失效(如果不走索引大概率是因为 where 查询范围过大导致的),但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖;
- 创建了组合索引,但查询条件未遵守最左匹配原则;
- 在索引列上进行计算、函数、类型转换等操作;
- 以 % 开头的 LIKE 查询比如 `LIKE '%abc';`;
- 查询条件中使用 OR且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
- IN 的取值范围较大时会导致索引失效,走全表扫描(NOT IN 和 IN 的失效场景相同);
- 发生[隐式转换](https://javaguide.cn/database/mysql/index-invalidation-caused-by-implicit-conversion.html);
- ~~使用 `SELECT *` 进行查询;~~ `SELECT *` 不会直接导致索引失效(如果不走索引大概率是因为 where 查询范围过大导致的),但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖
- 创建了组合索引,但查询条件未遵守最左匹配原则
- 在索引列上进行计算、函数、类型转换等操作
- 以 % 开头的 LIKE 查询比如 `LIKE '%abc';`
- 查询条件中使用 OR且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到
- IN 的取值范围较大时会导致索引失效,走全表扫描NOT IN 和 IN 的失效场景相同);
- 发生[隐式转换](https://javaguide.cn/database/mysql/index-invalidation-caused-by-implicit-conversion.html)
- ……
推荐阅读这篇文章:[美团暑期实习一面MySQl 索引失效的场景有哪些?](https://mp.weixin.qq.com/s/mwME3qukHBFul57WQLkOYg)。