您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦。
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通
❤️ 2. Python爬虫专栏,系统性的学习爬虫的知识点。9.9元买不了吃亏,买不了上当 。python爬虫入门进阶
❤️ 3. Ceph实战,从原理到实战应有尽有。 Ceph实战
❤️ 4. Java高并发编程入门,打卡学习Java高并发。 Java高并发编程入门
😁 5. 社区逛一逛,周周有福利,周周有惊喜。码农飞哥社区,飞跃计划
上一篇文章我们介绍了MySQL的存储引擎 【MySQL从入门到精通】【高级篇】(六)MySQL表的存储引擎,InnoDB与MyISAM的对比
前面介绍了MySQL默认采用的是InnoDB的存储引擎,而InnoDB存储引擎是一种事务型引擎,默认采用的是聚簇索引。
| 环境 | 版本 |
|---|---|
| Red Hat | 4.8.5-39 |
| MySQL | 5.7 |
索引是什么呢?熟悉又陌生的一个名词。想象这样一个场景,现在你要在图书馆里查找一本书,最古老的做法是一本一本书进行查找,直到找到自己想要的那本书。这种方式的缺点也是显而易见的,就是效率太低,图书馆里有成千上万的图书,一本本找要找到猴年马月呀。
那么图书馆是怎么做的呢?图书馆首先会根据图书类别将图书放在不同的区域,然后,在同一个类别下在根据字母顺序对图书进行排序这样当读者需要找书时就方便快捷多了。这种划分方式就可以理解成是索引。
再者,我们在数据中查找某部分内容时,是不是首先翻开目录,根据目录去找我们想要的内容,如果没有目录的话就只能一页页的去查找我们想要的内容了,这效率可想而知。这里的目录可以理解成索引。
同样的,在数据表中存在着数以万计的记录,查找某条记录的话如果一条条查找的话效率也比较低,为了提高查询效率就需要使用到索引了。
总而言之,索引是对数据库表中一列或多列的值进行排序的一种结构。MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。
首先,创建一个测试表
CREATE TABLE index_demo(
id int,
name varchar(10),
age int,
PRIMARY KEY(id)
)ROW_FORMAT=Compact;
这个新建的index_demo表有2个INT类型的列,1个varchar(10)类型的列,我们规定id列为主键,这个表的主键,这个表使用Compact行格式来实际存储记录。我们可以简化一下index_demo表的行格式示意图:

我们只在示意图展示记录的这几个部分:


mysql> insert into index_demo values(1,'张',10),(3,'李',20),(5,'孙',18);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0

那么这些记录已经按照主键值的大小串联成一个单向链表,如图所示:

如图中可以看出,index_demo表中3条记录都被插入到了编号为10的数据页中,此时我们再来插入一条记录。
mysql> INSERT INTO index_demo VALUES(4,'王',30);
Query OK, 1 row affected (0.00 sec)

因为页10最多只能放3条记录,所以我们不得不再分配一个新页:

注意,新分配的数据页编号可能并不是连续的,他们只是通过维护着上一页和下一页的编号而建立了链表关系。另外,页10中用户记录最大的主键值是5,而页20中有一条记录的主键值是4,因为5>4,所以这就不符合下一个数据页中用户记录的主键值大于上一个页中用户记录的主键值的要求,所以在插入主键值为4的记录的时候伴随着一次记录移动,也就是把主键值为5的记录移到页20中,然后,将主键值为4的记录移到页10中。

这个过程表明了在页中的记录进行增删改查操作的过程中,我们必须通过一些诸如记录移动的操作来始终保证这个状态一直成立;下一个数据页中用户记录的主键值必须大于上一个页中用户记录的主键值,这个过程我们称为页分裂。
给所有的页建立一个目录项。
由于数据页的编号可能是不连续的,所以在向index_demo表中插入许多条记录后,可能是这样的效果。

因为这些16KB的页在物理存储上时不连续的,所以如果想这么多页中根据主键值快速定位某些记录所在的页,我们需要给它们做个目录。每个页对应一个目录项,每个目录项包含下边两个部分:

上边称为一个简易的索引方案,是因为我们为了根据主键值进行查找时使用二分法快速定位具体的目录项而假设所有目录项都可以在物理存储器上连续存储,但是这样做有几个问题:

虽然说目录项记录中只存储主键值和对应的页号,比用户记录需要的存储空间小多了,但是无论怎么说一个页只有16KB大小,能存放的目录项也是有限的,那如果表中数据太多,以至于一个数据页不足以存放所有的目录项记录,如何处理呢?
这里我们假设一个存储目录项记录的页最多只能存放4条目录项记录,所以如果此时我们在向上图中插入一条主键值为320的用户记录的话,那就需要分配一个新的存储目录项记录的页:

问题来了,在这个查询步骤的第1步中我们需要定位存储目录项记录的页,但是这些页是不连续的,
如果我们表中的数据非常多则会产生很多存储目录项的页,那我们怎么根据主键值快速定位一个存储目录项记录的页呢?那就为这些存储目录项记录的页再生成一个更高级的目录,就像是一个多级目录一样,大目录里嵌套小目录,
小目录里才是实际的数据,所以现在各个页的示意图就是这样子:

如图,我们生成了一个存储更高级目录项的页33,这个页中的两条记录分别代表页30和页32,如果用户记录的主键值在[1,320]之间,则到页30中查找更详细的目录项记录,如果主键值不小于320的话,就到页32中查找更详细的目录项记录。
随着表中记录的增加,这个目录的层次会继续增加,如果简化一下,那么我们你可以用下边的这个图来描述它。

无论是存放用户的数据页,还是存放目录项记录的数据页,我们都把它们存放到B+树这个数据结构中了,所以我们也称这些数据页为节点。从图中可以看出,我们的实际用户记录其实都存放在B+树的最底层的节点上,这些节点也被称为叶子节点。其余用来存放目录项的节点称为非叶子节点或者内节点,其中B+树最上边的那个节点也称为根节点。
一个B+树的节点其实可以分为好多层,规定最下边的那层,也就是存放我们用户记录的那层为第0层,之后依次往上加。之前我们做了一个非常极端的假设:存放用户记录的页最多存放3条记录,存放目录项记录的页最多存放4条记录。其实真实环境中一个页存放的记录数量是非常大的,假设所有存放用户记录的叶子加点代表的数据页可以存放100条用户记录,所有存放目录项记录的内节点代表的数据页可以存放1000条目录项记录,那么:
本文首先通过设计一个简单的索引,层层迭代,最后引出了InnoDB的索引方案,它底层的数据结构是B+树的结构,叶子节点存放用户的记录,非叶子节点存放主键以及页编号。每个数据页可以存放16KB的数据。