前言:
最近学习了数据库这块的知识,看了一些视频,现在把学到的一些知识点整理出来,也为自己做个笔记。
索引是什么?
工作中,在数据库查询中,可能有些人觉得只要加了索引查询速度就很快,其实并不是这样子的。当然索引到底是什么东西呢?
索引:索引是帮助MySQL高效获取数据的排好序的数据结构,该数据结构是存储在文件中的。
上面的是一个对索引的一个解释,我们可以看出索引其实是一个数据结构。效果就是高效率获取结果。当然,这也是我们正确使用这个数据结构的结果。
索引的数据结构
索引的常用数据结构有
- 二叉树
- 红黑树
- hash
- BTree
索引查找数据的原理简述
通过索引文件加载出排好序的数据结构,比如获取一个id = 10,的那条数据,没有索引的情况下,是从id=1 遍历到id = 10这里需要查询10次。而有了索引真实通过数据结构去寻找,比如二叉树,这样就大大的减少了查找次数,找到id = 10这条索引里面存储了文件指针。文件指针指向的是id = 1000的这条记录的数据的位置。这样就可以提高很多倍的效率,当然不同的数据结构会有不同的效率。下文会继续分析。
这个就是一个二叉树查询的过程和步骤。查询第10条数据时,只需要查询5次就可以。
二叉树数据结构
二叉树的数据结构其实是不适合作索引的,虽然有一定程度的减少了查询的次数。但是效率并不是很高。
二叉树的查找数据的时间复杂度是O(log2N),查找时间适合深度有关系的,深度越高查找时间越长。但是二叉树如果要是已主键为索引,然后主键是自增的话,那么就会发生链表的情况,这样是没有减少查询次数,如果数据量大的,查询时间会很长,和没有使用索引几乎无差别的。
这是一个自增主键的二叉树数据结构。如果我查询id = 6的数据,那么我依然需要查询6次。如果说这是二叉树数据结构的缺点的话,那么红黑树刚刚好完善了二叉树的缺点。
红黑树数据结构
红黑树它平衡了二叉树,它具有一个自动平衡的功能,使得自增id不会成为链表状态。这样可以使查询时间减少。完美的避开了二叉树的致命缺陷。下面我们看一下红黑树的数据结构。
它不像二叉树那样,呈现链表状态,他能一定程度的减少查询次数,缩短查询时间。这个数据结构如果是内存中的话,那么运行效率是非常高,但是这个数据结构中在索引中,却不是很适合做索引的数据结构,这是为什么呢?
因为索引是存储在文件中的。刻在磁盘上面,数据量增加后,文件大小就会增加。如果是千万级的数据量那么文件会很大。而数据是没有办法一次性加载到内存中的。所以加载到内存中需要多次加载数据,每一次都不能确保数据在相同的磁道上,所以在存储数据的时候,需要尽量存在相同的磁道上面。磁盘读取数据时,是顺序读取的,这样是不会产生寻道时间,只有磁盘旋转。而红黑树没有顺序分布在磁盘中,所以查询的时候回出现不断地寻道。如果数据量大的情况下,会产生更多的寻道。这样在数据库查询中,会大大降低查询速度。
注意: 数据结构是内存中的逻辑结构而不是存储时候的物理结构,在磁盘上面数据是数组、杂乱的。这里需要说一下磁盘的存取原理。数据存储在磁盘的磁道上。然后磁盘是不停地转动的,如果数据在同一个磁道上面那么查询数据会非常快。如果要是在不同的磁道上面,那么需要磁头的移动,做寻道操作,而磁头的移动时间是非常高的。
HASH·散列数据结构
简单说一下用hash做索引,索引中是存着文件指针的。如果将一个主键的hash值作为文件指针,那么想象一下,比如我查询id = 6的数据 那么直接将6 做一次hash 得到hash值,这时候就已经得到文件地址了。直接一次文件IO操作,就能得到数据。这个速度是非常的块。但是索引没有用hash的原因是什么呢?
hash在查询一条数据情况下确实非常快。但是如果你查询 id>6 的数据hash就很无力了。因为没有办法查,其实就是走不了索引。
MySQL在InnoDB搜索引擎中有hash数据结构的索引。为什么会留着他,小编猜应该是在查询单条数据时候,hash的速度非常快吧,所以如果需要查询单条数据的时候,在数据库优化的时候不妨创建一个hash的索引试试,可能会有意想不到的结果。
B·Tree数据结构
- B-Tree的数据结构是每个节点可以存储多个数据的。
- B-Tree的数据结构的特点:
- 度(Degree)-节点的数据存储个数
- 所有的叶子节点的深度都是相同的。
- 叶子节点的指针为空
- 节点中的数据从左到右是递增排列的
这就是B-Tree的数据结构,你会发现这个是每个节点有多条数据,然后就是同样是10条数据,B-Tree的数据结构深度只有3 。每个节点中的数据都是一起更新到磁盘中的。所以这样会打打减短了深度。寻道的次数也就少了。这里有人会问,为什么不将度的值设置的很大,让他只有一层不就好了。这样所有的索引数据都是在内存中被计算那么速度应该会很快的哈。这里涉及到磁盘的操作。
从磁盘中读取数据:CPU查找数据,将查找的数据放在内存中。供CPU再次调度使用。如果内存中没有CPU需要的数据就回去磁盘读取数据,放进内存中。而磁盘和内存的数据交互有一个单位叫页。一页的大小是4k,磁盘和内存产生一次交互是以页为单位的,也就是4k的整数倍,但是这个大小是有上限的。
所以如果将所有索引放在一个节点上面,有可能数据量超过了磁盘和内存交互数据的最大值。这样这样我们就没有办法一次性拿出所有的数据放进内存了。所以这样会导致数据混乱。
这里度的值,MySQL是自动根据机器硬件做优化的。计量的将每页的大小增加到磁盘和内存数据交互的最大值。这样会将数据库的查询速度增加到最大。
下面我们观察B·Tree的数据结构 ,如果需求是我需要查询大于6的记录,那么查询起来好像也是有点复杂。最起码我需要截取0006右边的数据结构在将他排序然后查询,这样效率好像还是不是很高。MySQL就退出了B+Tree的数据结构。下面我们讲讲B+Tree的数据结构,看看它对于MySQL来说有哪些优点。
B+Tree数据结构
B+Tree 从名字上面看起来好像和B·Tree好像差不多。是的,B+Tree 只是B·Tree的变种。也就是在B·Tree的基础上新加了一些功能。
非叶子节点不存储数据,值存储key(也就是定义的索引字段) 非叶子节点之所以之存储key,是为了使非叶子节点的度更大,每次获取的数据更多。以此来增加查询速度。
叶子节点存在指针
顺序访问指针,提高区间访问的性能。
B+Tree在B·Tree的特性上面新加了这些特性。下面我们看看B+Tree的数据结构到底是什么样子的吧。
我们从上图可以看出,B+Tree数据结构在子叶节点拥有所有数据,我给数据结构添加了10个数字。下面也是顺序排列的10个数字。那么非子叶节点是干什么用的呢?假设我需要查抄大于等于7的记录那么我可以直接查到0007然后指向0009在指向0007就可以了。线面是有顺序指针的,有7,8指向9,10这样就可以直接去查找后面的数据了。不需要跨层级去查询。这样速度回提高很多。
B+Tree索引的性能分析
获取数据时进行了预读
预读:磁盘一般会顺序读取一定长度的数据(在B·Tree中见到的页的倍数)放入内存中。
局部性原理
局部性引用:当一条数据 被用到时,其附近的数据也通常马上会被使用
B+Tree 节点的大小设置为一页,每次新建节点时,直接申请一个页的空间,这样,每个节点的数据,物理上面也存储在一个页里,保证预读的顺利进行。读取数据是,获取一个节点的数据就只需要一次I/O。
B+Tree的度一般会超过100,这样保证深度不会太高(一般会在3~5h),这样也就保证了I/O的次数在3 - 5次之间。
那么我们看一下B+Tree是如何查询数据的。
第一次I/O:
第二次I/O:
第三次I/O:
这样我们就查出来了指定的数据,很快。那么我们如果需要查询大于等于8的时候,上面已经说到,B+Tree子叶节点有一个顺序指针,直接指向后面得位置,所以以8右边的数据查询很快。
MyISAM搜索引擎
MyISAM搜索引擎是非聚集的,它的索引文件和数据文件是分离的
MyISAM 是通过索引绑定的文件指针来查找到相应的数据的。
InnoDB搜索引擎
InnoDB索引实现了聚合
数据文件和索引文件是同一个文件
表数据文件本身B+Tree的索引结构文件
聚集索引叶子节点包含了完整的数据记录
为什么InnoDB必须有主键,为什么不用UUID,并且最好是整型自增的 ?
回答:因为InnoDB搜索引擎 是聚合的,表的数据中必然需要一个B+Tree的索引结构,而主键索引的叶子节点时表的数据,所以InnoDB 必须需要主键,否则无法聚合。
说到这里,可能会有很多小伙伴开始质疑,我在创建表的时候有的时候也忘记了创建主键呀,但是为什么一样能创建成功呢?为什么不报错。 那是因为为了用户的体验,MySQL在创建表的时候会判断,如果没有主键,那么就会从你创建表的字段中默认选择一个作为主键,如果没有可作为主键的字段,那么就会自动生成一个整型的 字段作为主键。(这里是看不到生成的字段的。)
InnoDB为什么必须要用自增ID呢?而不用UUID。 这里应该有些公司还是在使用UUID作为主键吧。我上家公司也是使用的UUID作为主键,但是这种主键有什么弊端呢 ?自增主键有什么优势呢?
UUID 是32长度的字符串,因为主键对于业务是无用的数据,如果数据量大的话,会占用很多无用的空间
由于UUID的占用的空间大,分配相同的空间导致度的个数减少,每次预读的内容减少,就有可能导致多进行I/O操作。这样会影响查询时间。
由于UUID是字符串,排序的时候,是使用ASCII排序的,比整型排序慢。
前面讲过,B+Tree在插入数据的时候,会直接开辟出一定量的内存,比如开辟了一页的内存,然后插入数据的时候,会将这一页的数据插入到磁盘里,这样就会在同一磁道上面,在查询的时候同一节点的数据不需要寻道,然后,思考一下,如果用UUID的话,如何保证,按照顺序排列,并且又能插入在同一磁道上面? 显然是不能的。所以这里需要自增的主键,这样才能保证每次插入的时候就能保证同一节点的数据是顺序的并且在同一磁道上面呢,这样在查询的时候才会不会寻道。查询速度才快。所以要用自增主键,而不用UUID。
为什么非主键索引结构叶子节点存储的是主键的值,而不是整条数据?待会我们回答这两个问题。
回答:主键索引默认就会存在,非主键索引是自己添加的,如果要是非主键索引也保存整条数据的话,会导致后面一些问题。
空间问题,大家都知道数据库的大部分空间都是数据所占用了。如果非主键索引多了,并且数据上升,那么空间占用是和非主键索引的个数成正比的。
数据冗余,两个索引,同一张表,数据存量分,显得数据非常冗余。.
数据一致性问题。在每次添加数据的时候,还要考虑到数据一致性的问题。每次修改都需要检查两边或更多遍数据是否一致,并且添加数据的时候,也需要添加两边。这样影响到数据添加的效率。
联合索引结构
联合索引的底层的数据结构是什么样子?
联合索引是将多个索引字段分成三部分,就是在插入索引的时候会根据最左前缀原理进行排序。
索引最左前缀原理
最左前缀原理是用在联合索引时才会产生。
是用来给联合索引做排序用的。是一种排序原则。
比如:user_id,user_name,create_time 这三个属性作为索引,那么排序的时候是先排序第一个,如果第一排除出来了。就不继续往后排序了。如果第一个相等,那么就继续往后排序。这样以此类推。
其实和sql的 select语句的排序是一个原理 比如
select user_id,user_name,create_time from sys_user order by user_id,user_name,create_time;
那么排序的顺序原理就是利用的最左前缀原理。
最后福利
有人要问了,看的什么视频?
私信我“Java”就能免费获取视频的领取方式了,还有更多Java资料,包括近一年面试的经历,我都整理成了PDF,也花了我挺多时间,希望大家帮我点赞支持一下!谢谢大家了!
点赞支持一下领取!