MySQL - 页

MySQL - 页,第1张

页是 InnoDB 管理存储空间的最小单位。一个页的大小一般是 16 KB。InnoDB 有许多种页用于不同的作用。其中数据页则是用于存储数据。数据页存储的内容为:

其中 Infimum + supremum 以及 User Records 为页中存储数据的部分。其中 Infimum 表示页中的最小记录,而 supremum 表示页中的最大记录。这两个记录不存储实际的值,而仅仅表示开头以及结尾。User Records 部分按行存储数据。User Records 中的每一条记录格式为:

插入到页中的记录是按主键大小进行排序。利用其中的 next_record 可以查找到下一条记录。在不考虑索引的情况下,如果我们要寻找其中的某条记录可以通过遍历链表的方式进行查找。但是如果当页中的数据过多,o(n) 的时间复杂度明显不满足快速查找的需求。因此 InnoDB 在页中设计了页目录。页目录中有多个槽,其规则如下:

因此实际搜索时,可以利用槽进行二分搜索,将算法复杂度降到了 。这个结构有点类似于一个两层的跳跃表。

由于一个页中实际能存储的数据有限,因此记录会被分配到多个页进行存储。页与页之间有着双向链表的结构。

在 innodb 中使用 B+ 树作为索引。实际上索引在 mysql 中也是作为页进行管理的。例如:

索引页与数据页类似,只是索引页中一条记录只存在两列。分别是页对应的最我号,以及页的页编号。当然,一个 b+ 树肯定存在多个级别,因此实际上的存存储格式为:

这里可以看出索引页与数据页其实并没有太多的区别。只不过数据页中存储着真实的数据,而索引页只存储索引。这里也可以看出主键索引实际上是聚集索引,当查找到最终的数据页时是可以直接获得数据。

许多个页组成的空间之为页空间。每个表空间对应着一个真实的文件 表名.ibd。每一个独立表空间中又会分为多个区。每一个区实际上是 64 个连续的页组成。每256个区划又会分为一组。

为什么会提出区的概念呢?原因是查找数据的时候,在页与页之间会通过双向链表进行查找。如果两个页随机分配物理地址,则其之间的物理位置可能非常远。那么在查找的时候无疑会形成大量的随机 IO。降低磁盘的性能。因此,当表中数据过大的时候,以区为单位进行分配连续的磁盘空间,可以减少随机 IO 的数量。

表空间中还有段的概念,当我们利用索引进行查询的时候。很多时候实际上是利用 B+ 树的叶子节点进行范围扫描。但是如果将索引页和数据页都存放在一个区中,那么数据页不一定是连续的磁盘空间。因此当进行范围扫描的时候又会存在随机 IO 的情况。因此索引页和数据页实际上是存放在不同的区中。存放索引页的区的集合又成为一个段,当然非索引页存放的区的集合则为另一个段。

我们知道,磁盘的速度是远远小于内存的速度。因此 InnoDB 会将查询的页缓存在内存 Buffer Pool 中,以免每一次请求都从磁盘中获取,加快查询速度。当然,内存不可能无止尽的使用。因此 InnoDB维护了一个 free 链表。 free 链表指向 Buffer Pool 中可用的部分。

当页面进行修改之后,缓存的中的页页不会马上落盘,这样的页称为脏页。InnoDB 维护了一个 flush 链表指向了脏页。当 buffer 的空间不足时,InnoDB 会进行刷页 *** 作,将脏页写入到磁盘中,腾出内存空间供新的页缓存使用。

一般来说,数据有冷热之分。如果经常刷新热点数据到磁盘中,肯定不划算。因为热点数据经常被查询修改,当写入到磁盘中后又会很快读入到缓存中,做了很多无用功。因此 InnoDB 采用了 LRU 算法统计哪些是热点数据,哪些是非热点数据。每次刷盘时从首先 LRU 链表的尾部将热点数据刷入到磁盘中。

InnoDB 并不是采用最简单的链表,而是划分区域的链表。其设计的原因是,InnoDB 在某些时候会采取预读的 *** 作,将一个区的数据全部读入到内存中。这些数据就会出现在 LRU 链表的头部。如果这些预读的数据最终不能被查询,那么真正的热点数据反而被挤到了链表的尾部,这样一旦存在预读行为 LRU 链表的功能就丧失了。同样,当用户进行扫描全表的 *** 作时,大量的页也会被加载到缓存中将 Buffer 占满。因此 InnoDB 将 LRU 分为两个区域-热数据(young 区)以及冷数据(old 区)。

对于第一种情况,当页被缓存到 Buffer 时首先会被放在 old 区。如果该页后续被继续访问,则会被放到 young 区中。而如果该页后续没有被继续访问到,则会逐渐移动到 old 区尾部。

对于扫描全表的情况,扫描全表有一个特点。即页中的每一条数据都会被访问到,同一个页第一次访问到最后一次访问的间隔时间一定很短。因此 InnoDB 设计了一个策略,如果当一个页加载到内存中,并且该页在第一此访问与最后一次访问间隔相差小于 1s (默认值),则该页就不会被加入到 young 区中。因此这种方式可以避免全表扫描时对 LRU 链表的污染。

当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。

不论是脏页还是干净页,都在内存中。

平时很快的更新 *** 作,都是在写内存和日志。

一条 SQL 语句,正常执行的时候特别快,但是有时也不知道怎么回事,它就会变得特别慢。

那这时候可能就是在刷脏页到磁盘中了~ flush

(1) InnoDB的redo log写满了。这时候系统会停止所有的更新 *** 作,然后让日志可以继续写。

把这部分数据日志都flush到磁盘上面。

(2) 也可能是系统内存不足,需要新的内存页,那么就淘汰一些内存页,空出来的给别的数据页使用。

先把脏页写到磁盘。

PS:使用内存是为了效率更好,

因为如果内存存在数据页,那么数据就一定正确,直接返回;

如果内存没有数据,才需要去磁盘中取,读入到内存,返回;

(3) MySQL 认为系统“空闲”的时候,反正闲着也是闲着hh

反正有机会就刷点数据

(4)MySQL 正常关闭。这时候,MySQL 会把内存的脏页都 flush 到磁盘上,这样下次 MySQL 启动的时候,就可以直接从磁盘上读数据,启动速度会很快。

3.1 如果是redo log写满了

尽量避免的。因为出现这种情况的时候,整个系统就不能再接受更新了,所有的更新都必须堵住。更新数为 0。

3.2 内存不够用了

常态,很正常。

3.3 buffer pool

因为innodb用的是buffer pool 管理内存,缓冲池中的内存页有三种状态:第一种是还没有使用的;第二种是使用了并且是干净页;第三种是使用了并且是脏页。

Innodb 的内存策略是尽量使用内存。

我觉得知道一下就好,这个脏页刷的快不快跟磁盘的能力有关。

可以通过innodb_io_capacity 这个参数设置磁盘能力。

InnoDB 的刷盘速度就是要参考这两个因素:一个是脏页比例,一个是 redo log 写盘速度。

平时要多关注脏页比例,不要让它经常接近 75%。

INNODB刷脏页,如果发现旁边也是脏页,那么会连带着一起刷掉。

所以可能会很慢,如果你的查询正好要先flush一个脏页的话。

在 InnoDB 中,innodb_flush_neighbors 参数就是用来控制这个行为的,值为 1 的时候会有上述的“连坐”机制,值为 0 时表示不找邻居,自己刷自己的。

找“邻居”这个优化在机械硬盘时代是很有意义的,可以减少很多随机 IO。机械硬盘的随机 IOPS 一般只有几百。

但是SSD 的IO很高,所以可以不用非要有刷写邻居的 *** 作,可以加快响应。

在 MySQL 8.0 中,innodb_flush_neighbors 参数的默认值已经是 0 了。

对比这个LSN跟checkpoint 的LSN,比checkpoint小的一定是干净页

也就是如果内存中比redolog的头部小,那么就是干净页

每个数据页有LSN,重做日志有LSN,checkpoint有LSN。

占用8字节,LSN主要用于发生crash时对数据进行recovery,LSN是一个一直递增的整型数字,表示事务写入到日志的字节总量。

LSN不仅只存在于重做日志中,在每个数据页头部也会有对应的LSN号,该LSN记录当前页最后一次修改的LSN号,用于在recovery时对比重做日志LSN号决定是否对该页进行恢复数据。前面说的checkpoint也是有LSN号记录的,LSN号串联起一个事务开始到恢复的过程。

感谢: https://www.cnblogs.com/drizzle-xu/p/9713378.html

我感觉就是可以理解为是一个long类型的数字,可以根据这个来比较要不要刷写数据,以及是不是干净页面,在恢复数据要拿这个进行比较。

缓存区域,缓存数据和索引在内存中。

innodb使用了一些链表。

lru链表:用来存储内存中的缓存数据。

free链表:用来存放所有的空闲页,每次需要数据页存储数据时,就首先检测free中有没有空闲的页来分配。

flush链表:在内存中被修改但还没有刷新到磁盘的数据页列表,就是所谓的脏页列表,内存中的数据跟对应的磁盘上的数据不一致,属于该列表的页面同样存在于lru列表中,但反之未必。

将脏页flush到磁盘上是直接将脏页数据覆盖到对应磁盘上的数据

经过前面的介绍现在我们都知道,一行一行的数据是存放在数据页里的,所以接下来我们该分析一下数据页的结构了。之前介绍过,每个数据页,实际上是默认有16kb的大小,那么这16kb的大小就是存放大量的数据行吗?明显不是的,其实一个数据页拆分成了很多个部分,大体上来说包含: 文件头、数据页头,最小记录和最大记录、多个数据行、空闲空间、数据页目录、文件尾部。下面我们来看一张图:

简单来说,就是平时我们创建的那些表,其实都有一个表空间的概念,在磁盘上都会对应着“表名.ibd”这样的一个磁盘数据文件。所以在物理层面,表空间就是对应一些磁盘上的数据文件。有的表空间,比如系统表空间可能对应的是多个磁盘文件,我们自己创建的表对应的表空间可能就是对应了一个“表名.ibd”数据文件。

在表空间的磁盘文件里会有很多的数据页,但是如果一个表空间包含了太多数据页的话就不便于管理,所以在表空间里又引入了一个 的概念,英文就是extent,一个数据区对应着连续的64个数据页,每个数据页是16kb,所以一个数据区是1mb,然后256个数据区被划分为一组。

对于表空间而言,它的第一组数据区的第一个数据区的前3个数据页都是固定的,里面存放了一些描述性的数据。比如fsp_hdr这个数据页,它里面就存放了表空间和这一组数据区的一些属性。ibuf_bitmap数据页,里面存放的是这一组数据页的所有insert buffer的一些信息。inode数据页,这里也存放了一些特殊信息。

我们现在先不去具体了解它们是干什么的,只要知道第一组数据区的第一个数据区的前3个数据页,都是存放一些特殊信息的。然后这个表空间里的其它各组数据区,每一组数据区的第一个数据区的头两个数据页都是存放特殊信息的,比如xdes数据页就是用来存放这一组数据区的一些相关属性的,其实就是很多描述这组数据区的东西。下面我们通过一张图来看一下表空间的存储结构。

1、linux *** 作系统的存储系统软件层原理分析以及IO调度优化原理

简单来说,linux的存储系统分为 VFS层、文件系统层,Page Cache缓存层,通用Block层、IO调度层、Block设备驱动层、Block设备层 ,如下图:

最后IO完成调度之后,就会决定哪个IO请求先执行,哪个IO请求后执行,此时可以执行的IO请求就会交给Block设备驱动层,最后经过驱动把IO请求发送给真正的存储硬件,也就是Block设备层。硬件设备完成IO读写 *** 作,最后就把响应经过上面的层级反向依次返回,最终MySQL可以得到本次IO读写 *** 作的结果。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/7486142.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-06
下一篇 2023-04-06

发表评论

登录后才能评论

评论列表(0条)

保存