(三)MySQL 索引

欢迎访问

什么是索引?

提高查询效率的一种数据结构,索引是数据的目录

索引的分类

  • 按「数据结构」分类:B+tree索引、Hash索引、Full-text索引
  • 按「物理存储」分类:聚簇索引、二级索引
  • 按「字段特性」分类:主键索引、唯一索引、普通索引、前缀索引
  • 按「字段个数」分类:单列索引、联合索引(复合索引)

按数据结构分类

为什么 MySQL InnoDB 选择 B+tree 作为索引的数据结构?

1、B+Tree vs B Tree

  • B+Tree 只在叶子节点存储数据,而 B 树 的非叶子节点也要存储数据,所以 B+Tree 的单个节点的数据量更小,在相同的磁盘 I/O 次数下,就能查询更多的节点。

  • B+ 树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让 B+ 树在插入、删除的效率都更高,比如删除根节点的时候,不会像 B 树那样会发生复杂的树的变化;

  • 另外,B+Tree 叶子节点采用的是双链表连接,适合 MySQL 中常见的基于范围的顺序查找,而 B 树无法做到这一点。

2、B+Tree vs 二叉树

  • B+树的节点相对较大,能够存储更多的索引项,从而减少了树的高度,降低了访问磁盘的次数(矮胖)
  • B+树的节点相对较大,一个节点可以存储多个关键字,这意味着在进行磁盘IO时,可以一次性读取更多的索引项到内存中,利用了局部性原理,减少了IO的开销(局部性)
  • B+树的节点相对较大,树的高度低,节点分裂和合并等平衡操作相对较少,在插入和删除操作时,B+树通常能够更加高效地维持树的平衡性,而不需要频繁地进行平衡调整操作
  • B+树的叶子节点形成了一个有序链表,便于范围查询

3、B+Tree vs Hash

Hash 在做等值查询的时候效率贼快,搜索复杂度为 O(1)。

但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因。

按物理存储分类

分为聚簇索引和二级索引

在查询时使用了二级索引,如果查询的数据能在二级索引里查询的到,那么就不需要回表,这个过程就是覆盖索引 。如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表

按字段特性分类

分为主键索引、唯一索引、普通索引、前缀索引

主键索引

通常在创建表的时候一起创建,一张表最多只有一个主键索引,索引列的值不允许有空值

唯一索引

一张表可以有多个唯一索引,索引列的值必须唯一,但是允许有空值

普通索引

既不要求字段为主键,也不要求字段为 UNIQUE

前缀索引

前缀索引是指对字符类型字段的前几个字符建立的索引,使用前缀索引的目的是为了减少索引占用的存储空间,提升查询效率

普通索引和唯一索引,应该怎么选择?

查询过程

  • 对于普通索引来说,查找到满足条件的第一个记录 (5,500) 后,需要查找下一个记录,直到碰到第一个不满足 k=5 条件的记录。
  • 对于唯一索引来说,由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。

更新过程

第一种情况是,这个记录要更新的目标页在内存中。这时,InnoDB 的处理流程如下:

  • 对于唯一索引来说,找到 3 和 5 之间的位置,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,找到 3 和 5 之间的位置,插入这个值,语句执行结束。

这样看来,普通索引和唯一索引对更新语句性能影响的差别,只是一个判断,只会耗费微小的 CPU 时间。

但,这不是我们关注的重点。

第二种情况是,这个记录要更新的目标页不在内存中。这时,InnoDB 的处理流程如下:

  • 对于唯一索引来说,需要将数据页读入内存,判断到没有冲突,插入这个值,语句执行结束;
  • 对于普通索引来说,则是将更新记录在 change buffer,语句执行就结束了。

将数据从磁盘读入内存涉及随机 IO 的访问,是数据库里面成本最高的操作之一。change buffer 因为减少了随机磁盘访问,所以对更新性能的提升是会很明显的。(因此,对于写多读少的业务来说,页面在写完以后马上被访问到的概率比较小,此时 change buffer 的使用效果最好)

按字段个数分类

分为单列索引、联合索引(复合索引)

使用联合索引时,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。在使用联合索引进行查询的时候,如果不遵循「最左匹配原则」,联合索引会失效

联合索引范围查询

联合索引的最左匹配原则,在遇到范围查询(如 >、<)的时候,就会停止匹配,也就是范围查询的字段可以用到联合索引,但是在范围查询字段的后面的字段无法用到联合索引。注意,对于 >=、<=、BETWEEN、like 前缀匹配的范围查询,并不会停止匹配,因为等于的时候可以用到索引

select * from t_table where a >= 1 and b = 2  //a=1等于时,b有序
索引下推

对于联合索引(a, b),在执行 select * from table where a > 1 and b = 2 语句的时候,只有 a 字段能用到索引

对于b=2的判断:

  • 在 MySQL 5.6 之前,回表之后判断
  • 而 MySQL 5.6 引入的索引下推优化(index condition pushdown),在搜索引擎判断,符合再回表
索引区分度

建立联合索引时,要把区分度大的字段排在前面,这样区分度大的字段越有可能被更多的 SQL 使用到。

区分度=不同的列/总列数

性别的区分度就很小,不适合建立索引或不适合排在联合索引列的靠前的位置,而 UUID 这类字段就比较适合做索引或排在联合索引列的靠前的位置

联合索引进行排序

这里出一个题目,针对针对下面这条 SQL,你怎么通过索引来提高查询效率呢?

sql 复制代码
select * from order where status = 1 order by create_time asc

给 status 和 create_time 列建立一个联合索引

什么时候需要 / 不需要创建索引?

索引最大的好处是提高查询速度,但是索引也是有缺点**(空间、维护)**的,比如:

  • 需要占用物理空间,数量越大,占用空间越大;
  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增大;
  • 会降低表的增删改的效率,因为每次增删改索引,B+ 树为了维护索引有序性,都需要进行动态维护。
什么时候适用索引?

唯一、常查、排序

  • 字段有唯一性限制的,比如商品编码;
  • 经常用于 WHERE 查询条件的字段,这样能够提高整个表的查询速度,如果查询条件不是一个字段,可以建立联合索引。
  • 经常用于 GROUP BYORDER BY 的字段,这样在查询的时候就不需要再去做一次排序了,因为我们都已经知道了建立索引之后在 B+Tree 中的记录都是排序好的
什么时候不需要创建索引?

不查不排、重复数据、数据量小、常更新

  • WHERE 条件,GROUP BYORDER BY 里用不到的字段,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。
  • 字段中存在大量重复数据;
  • 表数据太少的时候,不需要创建索引;
  • 经常更新的字段不用创建索引,比如不要对电商项目的用户余额建立索引,因为索引字段频繁修改,由于要维护 B+Tree的有序性,那么就需要频繁的重建索引,这个过程是会影响数据库性能的。

索引选择

首先,看MySQL 执行过程:

MySQL 数据库由 Server 层和 Engine 层组成:

  • Server 层有 SQL 分析器、SQL优化器、SQL 执行器,用于负责 SQL 语句的具体执行过程;
  • Engine 层负责存储具体的数据,如最常使用的 InnoDB 存储引擎,还有用于在内存中存储临时结果集的 TempTable 引擎。

SQL 优化器会分析所有可能的执行计划,选择成本最低的执行,这种优化器称之为:CBO(Cost-based Optimizer,基于成本的优化器)。

而在 MySQL中,一条 SQL 的计算成本计算如下所示:

java 复制代码
Cost  = Server Cost + Engine Cost

      = CPU Cost + IO Cost

其中,CPU Cost 表示计算的开销,比如索引键值的比较、记录值的比较、结果集的排序......这些操作都在 Server 层完成;

IO Cost 表示引擎层 IO 的开销,MySQL 8.0 可以通过区分一张表的数据是否在内存中,分别计算读取内存 IO 开销以及读取磁盘 IO 的开销。

总结来说:

  • MySQL 优化器是 CBO 的,MySQL 会选择成本最低的执行计划,你可以通过 EXPLAIN 命令查看每个 SQL 的成本;
  • 一般只对高选择度的字段和字段组合创建索引,低选择度的字段如性别,不创建索引;
  • 低选择性,但是数据存在倾斜,通过索引找出少部分数据,可以考虑创建索引。若数据存在倾斜,可以创建直方图,让优化器知道索引中数据的分布,进一步校准执行计划。

有什么优化索引的方法?

几种常见优化索引的方法:

  • 前缀索引优化;
  • 覆盖索引优化;
  • 主键索引最好是自增的;
  • 防止索引失效;

前缀索引优化

使用某个字段中字符串的前几个字符建立索引,减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。

不过,前缀索引有一定的局限性,例如:

  • order by 就无法使用前缀索引;
  • 无法把前缀索引用作覆盖索引;

覆盖索引优化

查询的数据能在二级索引里查询的到,不需要通过主键索引查询获得,可以避免回表的操作

假设我们只需要查询商品的名称、价格,有什么方式可以避免回表呢?

我们可以建立一个联合索引,即「商品ID、名称、价格」作为一个联合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。

所以,使用覆盖索引的好处就是,不需要查询出包含整行记录的所有信息,也就减少了大量的 I/O 操作。

主键索引最好是自增的

我们在建表的时候,都会默认将主键索引设置为自增的,具体为什么要这样做呢?又什么好处?

InnoDB 创建主键索引默认为聚簇索引,数据被存放在了 B+Tree 的叶子节点上。也就是说,同一个叶子节点内的各个数据是按主键顺序存放的,因此,每当有一条新的数据插入时,数据库会根据主键将其插入到对应的叶子节点中。

如果我们使用自增主键 ,那么每次插入的新数据就会按顺序添加到当前索引节点的位置,不需要移动已有的数据,当页面写满,就会自动开辟一个新页面。因为每次插入一条新记录,都是追加操作,不需要重新移动数据,因此这种插入数据的方法效率非常高。

如果我们使用非自增主键 ,由于每次插入主键的索引值都是随机的,因此每次插入新的数据时,就可能会插入到现有数据页中间的某个位置,这将不得不移动其它数据来满足新数据的插入,甚至需要从一个页面复制数据到另外一个页面,我们通常将这种情况称为页分裂页分裂还有可能会造成大量的内存碎片,导致索引结构不紧凑,从而影响查询效率

而如果记录是顺序插入的,例如插入数据11,则只需开辟新的数据页,也就不会发生页分裂:

索引最好设置为 NOT NULL

为了更好的利用索引,索引列要设置为 NOT NULL 约束。有两个原因:(优化器选择复杂,NULL有NULL值表占用空间)

  • 第一原因:索引列存在 NULL 就会导致优化器在做索引选择的时候更加复杂,更加难以优化,因为可为 NULL 的列会使索引、索引统计和值比较都更复杂,比如进行索引统计时,count 会省略值为NULL 的行。
  • 第二个原因:NULL 值是一个没意义的值,但是它会占用物理空间,所以会带来的存储空间的问题,因为 InnoDB 存储记录的时候,如果表中存在允许为 NULL 的字段,那么行格式中至少会用 1 字节空间存储 NULL 值列表,如下图的紫色部分

防止索引失效

发生索引失效的情况:

like和联合没有左匹配,函数,or

  • 当我们使用左或者左右模糊匹配的时候,也就是 like %xx 或者 like %xx%这两种方式都会造成索引失效;
  • 当我们在查询条件中对索引列做了计算、函数、类型转换操作,这些情况下都会造成索引失效;
  • 联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。
  • 在 WHERE 子句中,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

问题自测

1.为什么索引能提⾼查询速度?

索引是提高查询效率的一种数据结构,索引是数据的目录,索引的底层为B+树实现,B+Tree 只在叶子节点存储数据,单个节点的数据量小,且B+树矮胖,磁盘IO少

通过使用索引,数据库系统可以直接跳到存储数据的位置,而不必逐行查找

2.聚集索引和⾮聚集索引的区别?⾮聚集索引⼀定回表查询吗?

  1. 聚集索引(Clustered Index)
    • 聚集索引中,表的行数据按照索引的顺序进行存储,而且每张表只能有一个聚集索引
    • 因为数据行按照索引的顺序存储,所以聚集索引可以加速范围查询和排序操作,但是插入和更新操作可能会导致数据的重新排序,因此对于频繁进行这些操作的表来说,可能会影响性能。
  2. 非聚集索引(Non-Clustered Index)
    • 非聚集索引中,索引的叶子节点并不包含实际的数据行,而是包含指向数据行的指针(或者称为引用)。
    • 表可以有多个非聚集索引,这些索引可以加速检索

在查询时使用了二级索引,如果查询的数据能在二级索引里查询的到,那么就不需要回表,这个过程就是覆盖索引 。如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表

3.索引这么多优点,为什么不对表中的每⼀个列创建⼀个索引呢?(使⽤索引⼀定能提⾼查 询性能吗?)

索引最大的好处是提高查询速度,但是索引也是有缺点**(空间、维护)**的,比如:

  • 需要占用物理空间,数量越大,占用空间越大;
  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增大;
  • 会降低表的增删改的效率,因为每次增删改索引,B+ 树为了维护索引有序性,都需要进行动态维护。
什么时候适用索引?

唯一、常查、排序

  • 字段有唯一性限制的,比如商品编码;
  • 经常用于 WHERE 查询条件的字段,这样能够提高整个表的查询速度,如果查询条件不是一个字段,可以建立联合索引。
  • 经常用于 GROUP BYORDER BY 的字段,这样在查询的时候就不需要再去做一次排序了,因为我们都已经知道了建立索引之后在 B+Tree 中的记录都是排序好的
什么时候不需要创建索引?

不查不排、重复数据、数据量小、常更新

  • WHERE 条件,GROUP BYORDER BY 里用不到的字段,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。
  • 字段中存在大量重复数据;
  • 表数据太少的时候,不需要创建索引;
  • 经常更新的字段不用创建索引,比如不要对电商项目的用户余额建立索引,因为索引字段频繁修改,由于要维护 B+Tree的有序性,那么就需要频繁的重建索引,这个过程是会影响数据库性能的。

4.索引底层的数据结构了解么?Hash 索引和 B+树索引优劣分析

Hash 在做等值查询的时候效率贼快,搜索复杂度为 O(1)。

但是 Hash 表不适合做范围查询,它更适合做等值的查询,这也是 B+Tree 索引要比 Hash 表索引有着更广泛的适用场景的原因。

5.B+树做索引⽐红⿊树好在哪⾥?

红黑树通常用于实现内存中的数据结构,例如C++的STL中的map和set。它是一种高度平衡的二叉搜索树,对于内存中的数据结构来说,维护起来比较简单。

B+树则更适合用于实现磁盘存储上的索引结构:

  • 2、B+Tree vs 二叉树
    • B+树的节点相对较大,能够存储更多的索引项,从而减少了树的高度,降低了访问磁盘的次数(矮胖)
    • B+树的节点相对较大,一个节点可以存储多个关键字,这意味着在进行磁盘IO时,可以一次性读取更多的索引项到内存中,利用了局部性原理,减少了IO的开销(局部性)
    • B+树的节点相对较大,树的高度低,节点分裂和合并等平衡操作相对较少,在插入和删除操作时,B+树通常能够更加高效地维持树的平衡性,而不需要频繁地进行平衡调整操作
    • B+树的叶子节点形成了一个有序链表,便于范围查询

6.最左前缀匹配原则了解么?

联合索引要按照最左优先的方式进行索引的匹配

比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;

需要注意的是,因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。

但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:

  • where b=2;
  • where c=3;
  • where b=2 and c=3;

为什么联合索引不遵循最左匹配原则就会失效?

原因是,在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序

7.什么是覆盖索引

查询的数据能在二级索引里查询的到,不需要通过主键索引查询获得,可以避免回表的操作

假设我们只需要查询商品的名称、价格,有什么方式可以避免回表呢?

我们可以建立一个联合索引,即「商品ID、名称、价格」作为一个联合索引。如果索引中存在这些数据,查询将不会再次检索主键索引,从而避免回表。

8.如何查看某条 SQL 语句是否⽤到了索引?[待完善]

在这个查询前加上 EXPLAIN 来查看执行计划:

sql 复制代码
EXPLAIN SELECT * FROM my_table WHERE column_name = 'value';

这将会返回一个执行计划,其中包含了MySQL执行这个查询时使用的索引信息。如果在 Extra 列中看到 Using index 或者 Using where; Using index,那么表示查询使用了索引。如果没有使用索引,可能会看到 Using where 或者 Using filesort

相关推荐
C吴新科1 小时前
MySQL入门操作详解
mysql
Ai 编码助手3 小时前
MySQL中distinct与group by之间的性能进行比较
数据库·mysql
陈燚_重生之又为程序员4 小时前
基于梧桐数据库的实时数据分析解决方案
数据库·数据挖掘·数据分析
caridle4 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
白云如幻4 小时前
MySQL排序查询
数据库·mysql
萧鼎4 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
^velpro^4 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋34 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
荒川之神4 小时前
ORACLE _11G_R2_ASM 常用命令
数据库·oracle
IT培训中心-竺老师4 小时前
Oracle 23AI创建示例库
数据库·oracle