什么是Buffer Pull
BUffer Pull 是mysql的InnoDB引擎在启动时申请的连续内存空间,用来存放从磁盘中读取的页数据的。空间大小可以根据我们的需要自行设置,最小为5M。
为什么要有Buffer Pull来自
在计算机中,内存的运行速度和磁盘的运行速度可以说是差距十分明显,在操作上是从磁盘读取数据页返回给内存再从内存上进行处理,如果没操作完就把数据页写回磁盘,从时间的利用上就行十分浪费了。所以设计出了Buffer Pull来存储从磁盘中的数据页,操作完后再放回内存中而不是写回磁盘,这样就能提升资源的利用率了。
具体怎么样
Buffer Pull的结构
Buffer Pull的内存页大小和数据库的页大小一样都是16kb。同时,为了便于管理内存数据页,又额外设计了 控制信息(控制块) 来记录相关的信息。一个控制块对应一页。可以认为Buffer Pull中前部分数据是控制块,后部分数据是数据页,在中间会有一些用不上的内存碎片。
在初始化的时候会在Buffer Pull中创建好内存页和对应的控制块,控制块会根据对应页面的状态分成多种链表比如空闲链表和脏数据链表,分别记录还没有数据的页面和数据发生了修改还没写入磁盘的页面。两个链表都有一个基节点用来记录链表的开头、结尾节点和链表的节点数。值得注意的是这两个基节点并不在Buffer Pull中,也就是说,这两个节点是额外申请的空间。除此以外还有管理解压页的链表、处理使用频率的链表等。
在使用的时候会从多个数据页中查找数据,为了提升查找效率不至于每次都遍历一遍Buffer Pull,设计了一个HashMap来管理数据页。该HashMap以表空间号+页号作为key,内存页作为value,当我们想要查找某个数据页时就能通过这个HashMap来快速查找了。
链表管理
随着数据库的使用,空闲链表的值会越来越小,Buffer Pull中可以使用的空闲内存空间也就越来越少。当有一次要存储的页面数据超过了Buffer Pull的剩余空闲空间的时候就不得不舍弃掉一些有数据的链表了。
那么应该舍弃哪些部分呢?我们设计Buffer Pull就是为了缓存磁盘数据,减少磁盘IO以提升效率,从这个方面想,我们应该尽可能的舍弃掉最不会被使用的页面。
为此,需要建立链表来进行管理,Buffer Pull按照最近最少使用 的原则去淘汰缓存页 的,所以这个链表可以被称为 LRU链表。
一个简单的LRU链表规则如下:
- 新增的数据页放在链表的头部
- 使用到的数据页移动到链表的头部
有了这个LRU链表,我们就能知道那些数据页是不怎么被使用的,要进行淘汰的话先淘汰掉链表后面的数据就可以了。
但在实际的使用中会存在下面的问题:
- mysql存在预读机制,它会把读取页面的相关页面都读到内存中,如果这些预读页没有被使用到,那么我们就白白淘汰掉了一些可能会用到的数据,影响效率
- mysql的全表扫描会将全部数据页读到内存,但这种情况很大可能只会读一次,这样也会造成很大的浪费,而且还可能会把经常使用到的热点数据淘汰掉。
为了解决空间的浪费,mysql进一步设计链表,将LRU链表分成了两部分区域,前面的区域是yong 区,用于存储热点数据,后面的部分是old 区,用于存储非热点数据。两者是按照一定的比例进行划分的默认是old区占3/8,yong区占5/8
这样设计后有新页面要加入就可以先放在old区中,这样就不会影响yong区的热点数据,也就避免了热点数据被清除的情况,后续还会根据情况将old区中符合条件的数据转移到yong区。
因为yong区存储的是热点数据,所以读取会相当频繁,如果在这里每读取一次就改变一下头信息还是会浪费太多的资源,所以就进行很多优化:比如只有被访问的缓存页位于 young 区域的 1/4 的后边,才会被移动到 LRU链表 头部,这样就 可以降低调整 LRU链表 的频率,从而提升性能。
写入磁盘
后台有专门的线程每隔一段时间负责把脏页刷新到磁盘,这样可以不影响用户线程处理正常的请求。主要有两种 刷新路径: 从 LRU链表 的冷数
- 将LRU尾部的冷数据写入到磁盘
- 将脏数据页链表的部分数据写入到磁盘。