您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦。
💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通
❤️ 2. Python爬虫专栏,系统性的学习爬虫的知识点。9.9元买不了吃亏,买不了上当 。python爬虫入门进阶
❤️ 3. Ceph实战,从原理到实战应有尽有。 Ceph实战
❤️ 4. Java高并发编程入门,打卡学习Java高并发。 Java高并发编程入门
😁 5. 社区逛一逛,周周有福利,周周有惊喜。码农飞哥社区,飞跃计划
全网同名【码农飞哥】欢迎关注,个人VX: wei158556
文章目录
1. 简介2. 环境3. 根页面位置万年不动4. 内节点中目录记录的唯一性5. 一个页面最少存储2条记录总结1. 简介
上一篇文章我们介绍了聚簇索引,非聚簇索引以及联合索引【MySQL从入门到精通】【高级篇】(八)聚簇索引&非聚簇索引&联合索引。我们在介绍B+树索引的时候,是先把存储用户记录的叶子节点都画出来,然后接着画存储目录记录的内节点,实际上B+树的形成过程不是这样的。
2. 环境
3. 根页面位置万年不动
B+树的实际形成过程是这样的:
每当为某个表创建一个B+树索引(聚簇索引不是人为创建的,默认就有)的时候,都会为这个索引创建一个根节点页面。最开始表中没有数据的时候,每个B+树索引对应的根节点中既没有用户记录,也没有目录项记录。随后向表中插入用户记录时,先把用户记录存储到这个根节点中。当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页a中,然后对这个新页进行页分裂的操作,得到另一个新页,不如页b。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页a或者页b中,而根节点便升级为存储目录项记录的页。
这个过程特别注意的是:一个B+树索引的根节点自诞生之日起,便不会再移动。这样只要我们对某个表建立一个索引,那么它的根节点的页号便会被记录到某个地方,然后凡是InnoDB存储引擎需要用到这个索引的时候,都会从那个固定的地方取出根节点的页号,从而来访问这个索引。
4. 内节点中目录记录的唯一性
我们知道B+树索引的内节点中目录项记录的内容是索引列+页号的搭配,但是这个搭配对于二级索引来说不严谨,还拿index_demo表为例,假设这个表中的数据是这样的。
如果二级索引中目录项记录的内容只是索引列+页号的搭配的话,那么为age列建立索引后的B+树应该长这样:
如果我们想新插入一行记录,其中id,age,name的值分别是:(9,30,刘)
,那么在修改这个age列建立的二级索引对应的B+树时便碰到了个大问题:由于页3中存储的目录项记录是由age列+页号的值构成的,页3中的两条目录项记录对应的age列的值都是30,而我们新插入的这条记录的age页的值也是30,那么我们这条新插入的记录到底应该放到页4中,还是应该放到页5中呀,
为了让新插入记录能找到自己在那个页里,我们需要保证在B+树的同一层内节点的目录项记录除页号这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的。
索引列的值主键值页号
也就是我们把主键值也添加到二级索引内节点中的目录项记录了,这样就能保证B+树每一层节点中各条目录项记录除页号这个字段外是唯一的,所以我们为age列建立二级索引后的示意图实际上应该是这样子的:
这样我们再插入 时,由于页3中存储的目录项记录是由age列+主键+页号的值构成的,可以先把新记录的age列的值和页3中各目录项记录的age列的值作比较,如果age列的值相同的话,可以接着比较主键值,因为B+树同一层中不同目录项记录的age列+主键的值肯定是不一样的,所以最后肯定能定位唯一的一条目录项记录,在本例中最后确定新记录应该被插入到页5中。
5. 一个页面最少存储2条记录
一个B+树只需要很少的层级就可以轻松存储数亿条记录,查询速度相当不错,这是因为B+树本质上就是一个大的多层级目录,每经过一个目录时都会过滤掉许多无效的子目录,直到最后访问到存储真实数据的目录,那如果一个大的目录中只存放一个子目录是个啥效果呢?那就是目录层级非常非常多,而且最后的那个存放真实数据的目录中只能存放一条记录。费了半天劲只能存放一条真实的用户记录,所以InnoDB的一个数据页至少可以存放两条记录。
总结
面试必备的InnoDB的B+树索引的注意事项,根页面的位置万年不动,内节点中的目录项记录唯一,一个页面最少存储2条记录。