Buffer Pool内存数据结构

​ Buffer Pool的内存大小默认为128M,,磁盘中存放的数据页的默认大小为16KB,而Buffer Pool中存放的一个一个的数据页,我们通常叫做缓存页,因为毕竟Buffer Pool是一个缓冲池,里面的数据都是从磁盘缓存到内存去的。Buffer Pool中的缓存页的大小也和数据页相同为16KB,每个缓存页都有描述信息,其占数据页的5%左右,也就是大概800B,buffer pool真是数据结构可以由多个buffer pool组成,每个buffer pool可以由多个chunk组成,运行期间可以支持动态调整大小

如何知道哪些缓存页是空闲的

​ 数据库会为Buffer Pool设计一个free链表,他是一个双向链表数据结构,这个free链表里,每个节点就是一个空闲的缓存 页的描述数据块的地址,也就是说,只要你一个缓存页是空闲的,那么他的描述数据块就会被放入这个free链表中。

数据库启动的时候,如何初始化Buffer Pool

​ 数据库只要一启动,就会按照你设置的Buffer Pool大小,稍微再加大一点,去找操作系统申请一块内存区域,作为Buffer Pool的内存区域。 然后当内存区域申请完毕之后,数据库就会按照默认的缓存页的16KB的大小以及对应的800个字节左右的描述数据的大小,在Buffer Pool中划分出来一个一个的缓存页和一个一个的他们对应的描述数据。

将磁盘上的页读取到Buffer Pool的缓存页

​ 首先,我们需要从free链表里获取一个描述数据块,然后就可以对应的获取到这个描述数据块对应的空闲缓存页,接着我们就可以把磁盘上的数据页读取到对应的缓存页里去,同时把相关的一些描述数据写入缓存页的描述数据块里去,最后把那个描述数据块从free链表里去除就可以了

如何知道数据页有没有被缓存?

数据库还会有一个哈希表数据结构,他会用表空间号+数据页号,作为一个key,然后缓存页的地址作为value,当你要使用一个数据页的时候,通过“表空间号+数据页号”作为key去这个哈希表里查一下,如果没有就读取数据页,如果 已经有了,就说明数据页已经被缓存了

哪些缓存页是脏页以及如何被记录的?

​ 以数据库在这里引入了另外一个跟free链表类似的flush链表,这个flush链表本质也是通过缓存页的描述数据块中的两个指 针,让被修改过的缓存页的描述数据块,组成一个双向链表,凡是被修改过的缓存页,都会把他的描述数据块加入到flush链表中去,flush的意思就是这些都是脏页,后续都是要flush刷新到磁盘上去的

淘汰缓存页

​ 把修改过的缓存页刷到磁盘的数据页中,然后这个缓存页就可以情况了,让它变成一个空闲的缓存页。淘汰缓存页使用的是一个新的LRU链表,mysql的预读机制和全表扫描可能会频繁导致被访问的缓存页被淘汰的场景,全表扫描会将整个表的数据页加载到缓存中,导致LRU链表尾部经常被访问的缓存页被淘汰

哪些情况会触发mysql预读机制

  • 有一个参数是innodb_read_ahead_threshold,默认值是56,意思是如果顺序访问了一个区的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻的数据页都加载到缓存中去
  • 如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁会被访问到的,此时就会触发预读机制,把这个区的其它的数据页都加载到缓存中去,这个机制是通过参数innodb_random_read_ahead来控制的,默认是OFF关闭的

基于冷热数据分离的思想设计LRU链表

​ 真正的LRU链表会被拆分为两个部分,一部分是热数据,一部分是冷数据,这个冷热数据的比例是由innodb_old_blocks_pct参数控制的,默认是37,也就是冷数据的比例占37%

​ 数据页第一次被加载到缓存中时,会被挡在冷数据区域的链表头部位置,那么冷数据区域的缓存页是么时候被放到热数据区域呢?所以在mysql设定了一个规则,设置了一个innodb_old_blocks_time参数,它的默认是1000ms,也就是说当一个数据页被加载到缓存页之后,在1s之后,你再次访问这个缓存页才会被挪动到热区域的链表头部

​ 当缓存页不够时,会优先淘汰掉冷数据区域的链表尾部的缓存页,刷入磁盘。

LRU链表热数据区域的优化

​ 在热数据区域中,如果被访问了一个缓存页,会不会立马李东到热数据区域的链表头部?因为热数据区域是被经常访问的,所以总是进行移动会导致性能不好,也没有这个必要,所以LRU链表的热数据区域被优化了,只有在热数据区域的后3/4部分的缓存被访问了,才会移动到链表的头部,也就是说热数据区域的前1/4的缓存页被访问,是不会被移动到链表头部的

LRU链表冷数据区域的缓存页刷入磁盘的时机

  • 定时把LRU尾部的部分缓存页刷入磁盘,第一个时机并不是缓存页满的时候,才会挑选LRU冷数据区域尾部的几个缓存页刷入磁盘,而是有一个后台线程,运行一个定时任务,每个一段时间就会把LRU链表的冷数据区域的尾部一些缓存页刷入磁盘,清空这几个缓存页,把它们加入到free链表中,并从flush链表移除
  • 把flush链表中的一些缓存页定时刷入磁盘,仅仅将LRU冷数据区域的缓存页定时刷入磁盘是不够的,因为LRU链表的热数据区域里的很多缓存页被频繁修改,也是需要被刷入到磁盘的,所有这个后台线程也会在mysql不繁忙的时候,找个时间将flush链表中的缓存页刷入磁盘,这样被修改过的数据都会被刷入磁盘,只要flush链表中的缓存页被刷入了磁盘,那么缓存页也会从LRU链表中移除,然后加入到free链表中

实在没有空闲缓存页了怎么办?

​ 会从LRU冷数据区域的尾部找到一个缓存页,这个缓存页一定是最不经常被使用的,然后刷入磁盘,腾出来这个空闲的缓存页