索引
一、索引的引入
1. 什么是索引
索引是数据库存储引擎用来快速查找定位到数据的一种数据结构,索引是再存储引擎
中实现的。所有存储引擎支持每个表至少16个索引,总索引长度至少为256字节。有些存储引擎支持更多的索引数和更大的索引长度。MySQL在查找数据的时候,首先会检测查询的条件是否有索引,如果有索引,则通过索引查找相关数据,如果没有用到索引,则进行表数据一条一条的全表扫描,直到找到符合条件的数据。索引就好比书籍中的目录,通过目录中的标题和页码,能快速的找到相关的文章内容。
2. 为什么使用索引
-
在没有索引的时候,数据存放在磁盘是杂乱无章的,在查询数据的时候,一点规律的没有,查询时很耗时的。但是如果把这种杂乱的数据,进行有序排序之后,在查询数据的时候,就可以通过遍历列表就可以找到对应的数据。比如:想要查询【41】的数据,需要从89开始遍历,一条一条进行比较,直到【41】,才找到。这种数据少的时候还好,但是如果是上千条,上万条数据的话,时间复杂度O(n),此时就需要对有序的数据进行一个数据结构,降低查询的效率了。
-
如果把有序列表再转成
二叉树
的结构,二叉树的节点是以key-value
的格式存放数据,key存的是具体的值,value存的是该值对应的文件地址指针。这样再进行查询【25】的时候,可以通过遍历二叉树的形式进行查询,【25】和【34】比较,发现小于【34】,往左节点走,【25】比较,发现等于,则返回数据。这样我们只要比较两次,就找到了数据,效率得到很大的提升。
3. 索引的优缺点
3.1优点
- 索引提高了数据查询的效率,较低了数据库IO的成本。
- 创建数据表的唯一索引,可以保证数据表的每行数据的唯一性。
- 在进行连表查询的时候,有效的提高了表与表之间的连接速度。
- 在使用分组、排序查询的时候,给分组或排序字段建索引,可以减少分组或排序的时间,降低CPU的消耗。
3.2 缺点
- 创建索引和维护索引,都需要消耗时间,随着数据量的增加,维护索引的时间也同步增加。在第一次创建索引的时候,会比较耗时的。
- 索引是存放在磁盘上,索引索引也会占用一定的磁盘空间,如果有大量的索引,可能索引文件占用的磁盘空间,会大于数据占用的空间的。
- 索引虽然提高了查询的效率,但是对赠、删、改的速度降低了,因为更新操作,需要去维护索引。
二、索引的演进过程
1. 索引的设计
比如现在我们有一张数据表:student(id,name,age),这个表使用 Compact 行格式来实际存储记录的。
- record_type:位于信息头,表示记录类型,0-普通记录,1-目录项记录,2-最小记录,3-最大记录。
- next_record:位于信息头,表示指向下一条记录的偏移量。
- 其他信息:其他隐藏列的值以及记录的额外信息。
去掉【其他信息】后,将记录放到页
中:
2. 简化的页和记录
向表中插入几条数据,记录按主键顺序成功一个单项链表,id为主键:
INSERT INTO student(id,name,age) VALUES(1,'张三',23);
INSERT INTO student(id,name,age) VALUES(2,'李四',24);
INSERT INTO student(id,name,age) VALUES(3,'王五',25);
INSERT INTO student(id,name,age) VALUES(4,'赵六',26);
INSERT INTO student(id,name,age) VALUES(5,'宋七',27);
假设当前一个页最多能存放5条记录,然后我们再往表中插入数据时,由于页5存放不下,就会再分配新的页,这种情况称作页分裂
。
观察上图,我们发现页中的记录数,是通过单向链表进行有序的链接起来,而页与页之间,是通过双向链表
链接起来的。页与页之间的编号也可能不是连续的,是通过维护上一个页和下一个页的编号建立的链表关系。
- 有时候我们插入的数据id可能不是逐渐递增的,可能是:1,2,3,5,7这样的记录存放再页10中,当我们后面再添加一个id为4的时候,由于页中的记录是按主键递增的,所以
页分裂
的步骤为:
- 先分裂出页8,然后将id为4的数据存放到页8中,由于4小于【5,7】。
- 第二部是将记录【5,7】的记录移动到页8中,然后再将记录4移动到页5中。
- 最后才完成记录4的插入,这种就会出现
记录移动
。
- 通过这个过程,发现进行数据的增删改操作时,都需要通过记录移动来动态维护页记录。
3.建立目录项
当我们向数据表的数量量很之后,加载到内存的数据页就会很多,由于数据页的编号不是连续的,我们在查找记录所在的数据页时,非常困难。此时需要给数据页做一个目录,使数据页使有序的结构,这个目录就叫数据页的目录项。
每个目录项都有两个部分组成:
- key:存的使页中的最小记录的主键。
- page_no:存的使页的编号。
4. 建立目录项的记录页
多个目录项也是存放再一个数据页中的,再进一步推演目录项:
- 每个目录项之间也是通过单向链表进行连接的。
- 目录项的头信息中,有record_type和next_record,只是目录项的record_type是1。
- 目录项只有主键的主键的值和页编码列,而普通的记录有用户自定义的列,还有存储引擎的隐藏列。
- 普通记录和目录项都是一样的数据页,都会为主键值生成页目录,从而再查询的时候,按主键值进行二分查找,增加了查找的速度。
- 比如现在查找id为【9】的记录:
- 首先在目录项的页3中进行查找,使用的事二分法,快速定位:6<9<11,所以找到记录的页8。
- 再去普通记录的页8中查找,通过二分法快速找到id为9的记录。
当有有多个目录项的数据页的时候,也是通过双向链的方式,将两个数据页连接起来,也是通过页的分裂来实现的。
5. 建立目录项记录页的目录页
我们的目录项的记录页跟普通记录的页一样的,页与页之间并不是连续的,那么当目录项的记录页很多的时候,我们又要怎么做才能使快速通过主键,使查询数据的熟读更快呢?
此时,又可以为这个存储目录项的页生成一个更高级的目录页,像大目录嵌套这下一级目录,最小的目录才是实际存放记录的数据页。
如上图,在目录项的目录页上面,生成了一个更高级的页【30】。此时在进行查找的话,查找的主键位于[1,11)之间,就走页3目录项的页,如果大于11的话,走页7目录项的页。
最后形成的这个数据结构,称为B+树
。
简化结构: