【MySQL数据库】数据库必学——关于MySQL索引的基础知识都在这!

学习MySQL都逃不过索引,正确的使用索引可以优化增删改查等操作的效率。索引的知识不仅重要也很多,包括索引的模型,索引的存储方式以及主键索引普通索引等知识,都是需要了解了,我总结了自己学习索引的知识供大家参考学习。

一、 索引的常见模型

学习索引当然要先知道索引的底层是什么数据结构实现的,也就是索引模型。
首先来介绍三种常见的数据结构:hash、有序数组、搜索树

1.hash

hash相信大家其实已经不陌生了,我这里简单说一下:hash是以Key-value 形式的键值对存储的,每一个数据在存储时都会映射成一个key 存储在对应的位置,如果发生冲突,就在每个Key下缀一个链表来存储。只要输入对应的Key值就能得到存储的Value值,所以hash的查询复杂度为O(1),😮O(1)诶!很好的一个时间复杂度了,但是MySQL的索引却不是以hash形式实现的…
原因是hash中存储的元素都是无序的,而在数据库中,经常会进行区间查询,如果使用hash的方式只能进行全表扫描了😳,显然,这就失去了索引的优化意义了。
所以hash索引比较适合等值查询的情况!

2.有序数据

有序数组拥有很好的等值查询和区间查询的效率,在数组中由于数据有序存储,这样在搜索时可以使用二分查找时间复杂度为O(logn),其实这也算一个很好的复杂度了,而且也满足了区间查询,但MySQL索引仍然不是使用的有序数组…
但看查询效率确实很优秀,但如果进行插入和删除呢?那不得把大量数据进行搬移嘛😓!这样看有序数组也没有很方便。
所以有序数组更加适合静态存储的情况!

3. 搜索树

搜索树🌳呢有一个自己独有的特性,那就是对于这棵树,左孩子永远小于根节点,右孩子永远大于根节点。其中我们常见的就是二叉搜索树,对于二叉搜索树而言,搜索的时间复杂度可以达到O(logn)。当然为了一直保证是一颗二叉搜索树,就要维持搜索树成为平衡树,这样使得更新的时间复杂度也为O(logn)
搜索树可以是二叉的,也可以是N叉的,这就构成了B树,B树是可以在一个节点中存储多个数据,在节点内按照有序排列,也可以获得多个孩子。对于MySQL的InnoDB引擎实现索引来说呢,这个N的值一般是1200。你可能知道,具体的底层不是由B树构成的而是由 B+ 树为数据结构实现的。

首先我们要知道B+ 树的特点:B+树与B树最不同的就是拥有两个特性:

  • B+树每个节点的数据都会在子节点中出现,所以最终的叶子节点是存有整棵树所有的信息,而对一个数据的搜索也只能从从根节点一直走到叶子节点才能取到值。
  • B+ 树的叶子节点之间按照链表的方式连接在一次,方便了区域查询。

这里找了张B+树的图供大家参考:
在这里插入图片描述

好现在来解决这个问题(当然建议先搞明白B树和B+树的结构):
那为什么有了B树却还要使用B+树呢?

  1. IO次数减少
    在B+树中,除了叶子节点是保存数据的,其它节点只是一个索引,没有数据关联,这就使得同一个磁盘页中,可以存放更多的索引,也就是整个树会变得矮胖,也就减少了磁盘IO的数目
  2. 查询性能更加稳定
    由于B树在查询时,只要求找到该节点即可,可以树中,也可以是叶子节点,这就使得查询性能不稳定有着最好最坏一说。而对于B+树由于数据存放在叶子节点,所以无论找哪一个数据都是一定要遍历查找到叶子节点,也就是查询复杂度始终为树的高度级别
  3. 查询范围更加简便
    对于普通的B树而言,要想对一个范围查找,只能通过中序遍历,这就导致要多次进行加载磁盘上的数据,而对于B+树,只需要找到范围下限,通过叶子节点存在着链表相连可以直接查找

以上就解答了为什么使用B+树,也许你还有些模糊,让我再来解释解释:
🚩为什么要减少磁盘IO?

CPU、内存、磁盘IO 各自工作效率有着很大的插别,其中进行磁盘IO时效率最慢,所以就要尽量减少IO的次数。

🚩为什么说树变得矮胖,就减少了磁盘IO?

数据库的数据都是保存到磁盘上的,使用时再加载到内存中。而数据在磁盘上又是以页存储的,对于B+索引树,上面的每一个节点在磁盘上就是以一个数据页的方式来存储的。那么想一想,是不是在寻找数据时每一层都需要使用一个节点索引,也就是要加载一个数据页到内存,直到到达叶子节点。所以,树越高,需要磁盘IO就越多,使用B+树每个节点存放更多索引,这就很好的减少了树的高度,也就减少了IO次数

🚩为什么范围查找需要中序遍历?

这是搜索树的一个特性,对于一颗搜索树,中序遍历的结果就是树的有序序列,对于B树而言中序遍历需要多次跨层服务,而B+树由于叶子节点的设计就使得范围查询更加容易

好了,到这你大概对索引有了一个初步的认识,接着来看索引还有什么其它规则吧!

二、主键索引和普通索引

学习了索引的底层实现,接着我们来认识下主键索引和普通索引

主键索引的叶子节点存的是整行数据。在InnoDB里,主键索引也被称为聚簇索引(clustered index)
非主键索引的叶子节点内容是主键的值。在InnoDB里,非主键索引也被称为二级索引(secondary index)

这里需再啰嗦一下就是要分清楚主键索引和普通索引的存储内容:主键索引的叶子节点存放的是整行数据,而普通索引的叶子节点存放的是对应的主键的值
所以如果我们使用的是主键索引搜索数据,那么在遍历到叶子节点的时候就可以直接取到该行的数据。而对于普通索引,在搜索到叶子节点时,找到的是对于数据的主键索引的值,此时再通过这个主键值再次到主键索引树上搜索,直到找到主键索引树上叶子节点中的行数据。这个过程称之为回表。所以我们通常要尽量使用主键索引避免产生回表,降低效率。

对于主键索引,我们可以选择表中的一个字段作为主键,也可以使用自增主键,自增主键就是每插入一行数据,索引的值都会有序加一,如果我们没有指定主键索引,那么MySQL会自动创建一个自增主键。那么这就有了另一个问题:
什么时候使用字段直接做主键索引,什么时候又要使用自增主键做主键索引呢?

  1. 使用自增主键:我们已经知道,如果创建了二级索引,那么二级索引的叶子节点存放的数据就是主键的ID,对于一个使用自增主键的表而言,自增主键占用的大小往往要比业务字段占用的大小更小。这样在建立多个普通索引后, 使用自增主键大大减少了叶子节点存放占用的内存。同时由于自增主键都是有序增加的,插入时不容易导致磁盘页的分裂和合并。所以从性能和占用内存方面看,自增主键往往更加合理。
  2. 使用业务字段做主键:当然,如果在业务逻辑中已经可以保证表中只有一个索引,这个索引也是一个唯一索引,那么也就不用考虑普通索引带来的存储问题了,这时建议使用业务字段做索引,可以避免每次的回表。

你可能还有一些疑问,让我再来解释解释:
🚩什么是磁盘页的合并以分裂?

B+树为了维护索引有序性,在插入新值的时候需要做必要的维护。当插入一个新值时,如果发现,该值位于一页数据的中间,并且这一页数据已经满了,此时根据B+树的算法,这时候需要申请一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂。在这种情况下,性能自然会受影响。当然有分裂就有合并。当相邻两个页由于删除了数据,利用率很低之后,会将数据页做合并。合并的过程,可以认为是分裂过程的逆过程。

如果你已经清楚了,那就继续往下看吧!

三、索引实现的优化

这一小节,我来讲讲关于使用索引时它实现的优化吧。

1.覆盖索引

上面我们已经给大家提到了回表的概念,那是不是无论查找什么都会回表呢?
其实也不是,如果普通索引中刚好包含了搜索语句中所需要的值,就会直接取得返回,而不会再次回表。
举个例子吧:假设是一个student学生表,包含学生的学号、姓名、年龄、成绩。这里的主键索引是学号,而普通索引是姓名。
在这里插入图片描述
当执行selsct ID from student where name = "小兰"时,使用的是姓名索引,当查找到叶子节点时,会发现叶子节点保存的就是需要的“小兰”的ID,所以此时就会直接返回,而不会再次进行回表操作,这样是不是就提升了效率呀👍!

由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。

2.最左前缀

到这里你一定有一个疑问,如果为每一种查询都设计一个索引,索引是不是太多了,而如果不创建,那就只能全表扫描,也有点夸张。这里就引入了最左前缀原则。
别急,我们先来认识另一种索引联合索引,联合索引就是多个字段同时做为索引。这里我们用(name,age)这个联合索引来分析。这个索引可以通过上面讲的覆盖索引原则查询name和age之间的关系。不仅如此,如果我们的SQL语句仅仅是查询"where name like ‘小%’",此时就会使用最左前缀原则,用这个(name,age)索引来查到符合记录的数据行。但是如果你想单独查询age字段,那就用不到这个索引了得新建一个age的单独索引了,毕竟是最左前缀嘛👈,是根据索引的左边匹配的。

知道了最左前缀原则后,那就自然有个问题:在建立联合索引的时候,如何安排索引内的字段顺序?
这里我们的评估标准是,索引的复用能力。因为可以支持最左前缀,所以当已经有了(a,b)这个联合索引后,一般就不需要单独在a上建立索引了。因此,第一原则是,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。
那么,如果既有联合查询,又有基于a、b各自的查询呢?此时就要考虑空间的问题了。怎么说呢?就是如果a字段占用的内存比b字段大,那么你觉得是(a,b)(b)呢 ?🆚还是(b,a)(a)呢? 当然是前者啦!额外建立的索引占的空间更小当然就更好。

3. 索引下推

现在又有一个问题,对于上面的学生表,以(name,age)索引为例,如果现在要检索出表中“名字第一个字是小,而且年龄是18岁的成绩大于90分的人”该怎么做呢?

  • 首先使用这个索引,检索出名字第一个字是小的所有人。
    在这里插入图片描述
  • 在MySQL 5.6之前呢,检索出这三个人后,就要开始回表,在主键索引种找到行数据然后对比字段是否符合,因此要回三次表。看起来是不是笨笨的!🐷
  • 所以在5.6之后呢,就有了索引下推原则。查找到满足条件的三个人后还会在这个联合索引上继续判断age字段是不是满足需求,不满足就舍弃,满足age的要求才会回表。也就是只会回两次表。这样也就大大的优化了效率!

这一小节概念也比较多,

四、字符串索引

给普通字段加索引就是整个的字段都作为索引,那给字符串字段加索引也是一定把整个字段都加上了索引嘛?这一小节,就来探讨关于字符串索引的相关知识点吧!
对于字符串索引,如果不指定长度,会默认将整个字符串都作为索引,但这样就会占用很大的内存空间,所以我们也可以通过指定字符串索引的长度来规定索引包含的字符串的长度。
现在有学生表,要在一个字符串类型的email字段上加索引,可以通过下面两种方式:

alter table student add index index1(email);
alter table student add index index2(email(6)); 

第二种方式就是只取了email的前六个字符作为了索引。

两种方式各有利弊:

  • 如果使用index1那么显然存储空间会较大,但在查询时,如果数据的前缀相似性很大,也可以精确搜索。
  • 对于index2,占用空间是小,但是相应的,对于前缀相似性大的字段,它就会相应的多搜索几条数据,也就会回表几次。

前缀索引对覆盖索引的影响
使用前缀索引还会影响到覆盖索引,举个例子:
select ID, email from student where email = '[email protected]';
此时你对比着看看使用index1和使用index2的区别,如果使用index1,是不是由于覆盖索引的原则,就可以直接从普通索引获取到数据而不需要回表,而对于index2就还要先根据email找出满足条件的ID然后进行回表操作。这样看来使用字符串的前缀索引也会带来一些性能的降低。
这里还要提一点,假设字段总长度为10,那么如果在定义索引时是明确指定10位的,也就是其实是把整个字符串都包含进去了,但是就像上面情况发生时,还是会回表😓因为MySQL并不能确定已经完全包含了整个字段的信息。

其它方式的字符串索引:
既然说到前缀相似性大,那么就有问题了,如果前缀有极大的相似性要怎么办呢?比如身份证号,又或者学校中学生的学号,每一种前缀都对应很多条数据,这时候有没有什么方法呢?

还是有的。

  • 第一种方式是使用倒序存储。如果你存储身份证号的时候把它倒过来存,每次查询的时候,你可以这么写:
mysql> select field_list from t where id_card = reverse('input_id_card_string');

在每次输入的身份证号码进行反转,然后查询。由于身份证号码的后面位数的区分度比前缀大,所以这种方式也是很可观的。

  • 第二种方式是使用hash字段。你可以在表上再创建一个整数字段,来保存身份证的校验码,同时在这个字段上创建索引。

⏩介绍了两种方法后,再看看它们之间的区别:

  1. 从占用的额外空间来看,倒序存储方式在主键索引上,不会消耗额外的存储空间,而hash字段方法需要增加一个字段。当然,倒序存储方式使用4个字节的前缀长度应该是不够的,如果再长一点,这个消耗跟额外这个hash字段也差不多抵消了。

  2. 在CPU消耗方面,倒序方式每次写和读的时候,都需要额外调用一次reverse函数,而hash字段的方式需要额外调用一次crc32()函数。如果只从这两个函数的计算复杂度来看的话,reverse函数额外消耗的CPU资源会更小些。

  3. 从查询效率上看,使用hash字段方式的查询性能相对更稳定一些。虽然有冲突的概率,但是概率非常小,可以认为每次查询的平均扫描行数接近1。而倒序存储方式毕竟还是用的前缀索引的方式,也就是说还是会增加扫描行数。

总之使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。🔍

好了,这次的内容就是这些啦,其实也只是简单的总结了一下自己的学习成果,很多细节还是应该慢慢逐渐的推敲。现在慢慢发现,学的越多,越能把很多知识串起来的感觉太棒了! 不过自己还是有很多的不足,文章如果有什么问题也欢迎大家指正,希望自己表达的能对你有所帮助,也欢迎大家点赞关注一起进步!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章