写点什么

15 张图搞定 MySQL InnoDB 工作原理,kafka 视频分析

用户头像
极客good
关注
发布于: 刚刚

我们可以把一批数据放在一起。


写操作时,先将数据写到内存的某个批次中,然后再将该批次的数据一次性刷到磁盘上。如下图所示:



读操作时,从磁盘上一次读一批数据,然后加载到内存当中,以后就在内存中操作。如下图所示:



将内存中的数据刷到磁盘,或者将磁盘中的数据加载到内存,都是以批次为单位,这个批次就是我们常说的:数据页。


当然 innodb 中存在多种不同类型的页,数据页只是其中一种,我们在这里重点介绍一下数据页。


那么问题来了,什么是数据页?


数据页主要是用来存储表中记录的,它在磁盘中是用双向链表相连的,方


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


便查找,能够非常快速得从一个数据页,定位到另一个数据页。


很多时候,由于我们表中的数据比较多,在磁盘中可能存放在多个数据页当中。


有一天,我们要根据某个条件查询数据时,需要从一个数据页找到另一个数据页,这时候的双向链表就派上大用场了。磁盘中各数据页的整体结构如下图所示:



通常情况下,单个数据页默认的大小是 16kb。当然,我们也可以通过参数:innodb_page_size,来重新设置大小。不过,一般情况下,用它的默认值就够了。


好吧,数据页的整体结构已经搞明白了。


那么,单个数据页包含哪些内容呢?



从上图中可以看出,数据页主要包含如下几个部分:


  • 文件头部

  • 页头部

  • 最大和最小记录

  • 用户记录

  • 空闲空间

  • 页目录

  • 文件尾部


3.用户记录


======


对于新申请的数据页,用户记录是空的。当插入数据时,innodb 会将一部分空闲空间分配给用户记录。


用户记录是 innodb 的重中之重,我们平时保存到数据库中的数据,就存储在它里面。那么,它里面又包含哪些内容呢?你不好奇吗?


其实在 innodb 支持的数据行格式有四种:


  1. compact 行格式

  2. redundant 行格式

  3. dynamic 行格式

  4. compressed 行格式


我们以 compact 行格式为例:



一条用户记录主要包含三部分内容:


  1. 记录额外信息,它包含了变长字段、null 值列表和记录头信息。

  2. 隐藏列,它包含了行 id、事务 id 和回滚点。

  3. 真正的数据列,包含真正的用户数据,可以有很多列。


下面让我们一起了解一下这些内容。


3.1 额外信息


========


额外信息并非真正的用户数据,它是为了辅助存数据用的。


3.1.1 变长字段列表


============


有些数据如果直接存会有问题,比如:如果某个字段是 varchar 或 text 类型,它的长度不固定,可以根据存入数据的长度不同,而随之变化。


如果不在一个地方记录数据真正的长度,innodb 很可能不知道要分配多少空间。假如都按某个固定长度分配空间,但实际数据又没占多少空间,岂不是会浪费?


所以,需要在变长字段中记录某个变长字段占用的字节数,方便按需分配空间。


3.1.2 null 值列表


=============


数据库中有些字段的值允许为 null,如果把每个字段的 null 值,都保存到用户记录中,显然有些浪费存储空间。


有没有办法只简单的标记一下,不存储实际的 null 值呢?


答案:将为 null 的字段保存到 null 值列表。


在列表中用二进制的值 1,表示该字段允许为 null,用 0 表示不允许为 null。它只占用了 1 位,就能表示某个字符是否为 null,确实可以节省很多存储空间。


3.1.3 记录头信息


===========


记录头信息用于描述一些特殊的属性。



它主要包含:


  • deleted_flag:即删除标记,用于标记该记录是否被删除了。

  • min_rec_flag:即最小目录标记,它是非叶子节点中的最小目录标记。

  • n_owned:即拥有的记录数,记录该组索引记录的条数。

  • heap_no:即堆上的位置,它表示当前记录在堆上的位置。

  • record_type:即记录类型,其中:0 表示普通记录,1 表示非叶子节点,2 表示 Infrimum 记录, 3 表示 Supremum 记录。

  • next_record:即下一条记录的位置。


3.2 隐藏列


=======


数据库在保存一条用户记录时,会自动创建一些隐藏列。如下图所示:



目前 innodb 自动创建的隐藏列有三种:


  • db_row_id,即行 id,它是一条记录的唯一标识。

  • db_trx_id,即事务 id,它是事务的唯一标识。

  • db_roll_ptr,即回滚点,它用于事务回滚。


如果表中有主键,则用主键做行 id,无需额外创建。如果表中没有主键,假如有不为 null 的 unique 唯一键,则用它做为行 id,同样无需额外创建。


如果表中既没有主键,又没有唯一键,则数据库会自动创建行 id。


也就是说在 innodb 中,隐藏列中事务 id 和回滚点是一定会被创建的,但行 id 要根据实际情况决定。


3.3 真正数据列


=========


真正的数据列中存储了用户的真实数据,它可以包含很多列的数据。这个比较简单,没有什么好多说的。


3.4 用户记录是如何相连的?


===============


通过上面介绍的内容,大家对一条用户记录是如何存储的,应该有了一定的认识。


但问题来了,一条用户记录和另一条用户记录是如何相连的,innodb 是怎么知道,某条记录的下一条记录是谁?


答案是:用前面提到过的, 记录额外信息 》 记录头信息 》下一条记录的位置。



多条用户记录之间通过下一条记录的位置,组成了一个单向链表。这样就能从前往后,找到所有的记录了。


4.最大和最小记录


=========


从上面可以得知,在一个数据页当中,如果存在多条用户记录,它们是通过下一条记录的位置相连的。


不过有个问题:如果才能快速找到最大的记录和最小的记录呢?


这就需要在保存用户记录的同时,也保存最大和最小记录了。


最大记录保存到 Supremum 记录中。


最小记录保存在 Infimum 记录中。


在保存用户记录时,数据库会自动创建两条额外的记录:Supremum 和 Infimum。它们之间的关系,如下图所示:



从图中可以看出用户数据是从最小记录开始,通过下一条记录的位置,从小到大,一步步查找,最后找到最大记录为止。


5.页目录


=====


从上面可以看出,如果我们要查询某条记录的话,数据库会从最小记录开始,一条条查找所有记录。如果中途找到了,则直接返回该记录。如果一直找到最大记录,还没有找到想要的记录,则返回空。


咋一看,没有问题。


但如果仔细想想。


效率会不会有点低?


这不是要对整页用户数据进行扫描吗?


有没有更高效的方法?


这就需要使用页目录了。


说白了,就是把一页用户记录分为若干组,每一组的最大记录都保存到一个地方,这个地方就是页目录。每一组的最大记录叫做槽。


由此可见,页目录是由多个槽组成的。如下图所示:



假设一页的数据分为 4 组,这样在页目录中,就对应了 4 个槽,每个槽中都保存了该组数据的最大值。


这样就能通过二分查找,比较槽中的记录跟需要找到的记录的大小。如果用户需要查找的记录,小于当前槽中的记录,则向上查找上一个槽。如果用户需要查找的记录,大于当前槽中的记录,则向下查找下一个槽。


如此一来,就能通过二分查找,快速的定位需要查找的记录了。


so easy


6.文件头部和尾部

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
15张图搞定MySQL InnoDB工作原理,kafka视频分析