「这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战」
整体结构
Buffer Pool 本质是数据库的一个内存组件,Buffer Pool默认是128MB,可以通过下面这个配置调整大小
[server]
innodb_buffer_pool_size = 2147483648
在数据库中核心数据模式是:表+字段+行的概念,数据库中有多个表,一个表有很多行数据,每行数据都有自己的字段值,但是在Buffer Pool中数据并不是一行行存储,而是对数据抽象出来了一个数据页的概念,把很多行数据放在一个数据页里,一个Buffer Pool 存放很多页数据,所以在更新一行数据的时候,会先找到这行数据所在的数据页,然后从磁盘中将所在的数据页直接加载到Buffer Pool里,在更新对应的数据。
实际默认情况下,磁盘中存放的数据页大小是16KB,也就是一页面包含了16KB的内容,而Buffer Pool中存放的一个个数据页在默认情况下,一个缓存页的大小和磁盘上的数据页大小一一对应都是16KB。 每个缓存页都有一个描述信息,包含:这个数据页所在的表空间,数据页的编号,这个缓存页在Buffer Pool中的地址以及一些其他信息,这个描述信息本身也是一块数据,在Buffer Pool中,每个缓存页的描述数据放在最前面。 Buffer Pool中的描述数据大概相当于缓存页大小的5%左右,也就是每个描述数据大概是800个字节左右的大小,然后假设你设置的buffer pool大小是128MB,实际上Buffer Pool真正的最终大小会超出一些,可能有个130多MB的样子,因为他里面还要存放每个缓存页的描述数据。
free链表
数据库启动的时候,会按照设定的Buffer Pool大小的基础上稍微增大点,去找操作系统申请一块内存空间,作为Buffer Pool的内存区域,当内存申请完毕之后,数据库会按照默认的缓存页16KB的大小以及对应的800字节左右的描述数据的大小在Buffer Pool中划分出一个个的缓存页及对应的描述数据。这时候缓存页都是空的,当对数据进行CRUD的时候,才会把数据从磁盘中加载到缓存页中。 为了很清楚的知道哪些缓存页是空闲的,设计了free 链表,是一个双向链表的数据结构,在这个free链表里,每个节点都是一个空闲的缓存页描述数据块的地址,只要一个缓存页是空闲的,那么它的描述数据地址就会加入到这个free链表中。 free链表,他本身其实就是由Buffer Pool里的描述数据块组成的,你可以认为是每个描述数据块里都有两个指针,一个是free_pre,一个是free_next,分别指向自己的上一个free链表的节点,以及下一个free链表的节点。 对于free链表而言,只有一个基础节点是不属于Buffer Pool的,他是40字节大小的一个节点,里面就存放了free链表的头节点 的地址,尾节点的地址,还有free链表里当前有多少个节点。 当需要将数据页读取到Buffer Pool中的缓存页里去的时候,首先从free链表中获取一个描述数据块,然后就可以对应的获取到这个描述数据块对应的空闲缓存页,接着把磁盘上的数据页读取到对应的缓存里去,同时把相关的描述数据写入缓存页的描述数据块里去,最后把那个描述数据从free链表里去除就可以。
当执行CRUD的时候,会先查看数据页有没有被缓存,如果没被缓存就会走缓存数据逻辑,从free链表中找到一个空闲的缓存页,从磁盘上读取数据页写入缓存页,写入描述数据,从free链表中移除这个描述数据块,如果数据页被缓存则会直接使用。 数据库还会有一个哈希表数据结构,他会用表空间号+数据页号,作为一个key,然后缓存页的地址作为value。 当你要使用一个数据页的时候,通过“表空间号+数据页号”作为key去这个哈希表里查一下,如果没有就读取数据页,如果已经有了,就说明数据页已经被缓存。
flush链表
当执行增删改的时候,会修改Buffer Pool中的缓存页数据,而不会直接修改磁盘上的数据,这个时候就会造成缓存页的数据与磁盘里的数据不一致,这种情况称缓存页为脏数据,脏页。 为了知道哪些页为脏页,数据块引入了flush链表, 这个链表本质也是通过缓存页的描述数据中的两个指针,让被修改的缓存页描述数据块,组成一个双向链表,凡是被修改过的数据页都会加入到flush链表中。
lru链表
当不停的把磁盘上的数据页加载到空闲缓存页中时,free链表中的空闲缓存页会越来越少,随着时间的推移,free链表中的空闲缓存页会没有,这个时候就需要将缓存页输入收入磁盘,腾出一部分空闲页,MySQL引入了LRU链表来把不常访问的缓存页刷入磁盘中。通过LRU链表可以将不常用的缓存页刷入磁盘,但是怎么判定哪些缓存页呢?这就要用到MySQL改进版的lru算法。
基于free链表、flush链表,lru链表机制,当数据加载到缓存页时,free链表会移除这个缓存页,然后lru链表在冷数据区域的头部放入这个缓存页,如果对缓存页进行了修改,flush链表会记录这个脏页,lru链表可能会对数据进行移动(冷数据区--->热数据区,热数据区---->热数据区头部)。并不是缓存页满的时候,才会把LRU冷数据尾部的几个缓存页刷入磁盘,而是有一个后台线程,他会运行一个定时任务,这个定时任务每隔一段时间就会把LRU链表的冷数据区域的尾部的一些缓存页,刷入磁盘里去,清空这几个缓存页,把他们加入回free链表去。 也有一个后台线程,在MySQL系统不忙的时候,将flush链表中的缓存页刷入磁盘中,只要flush链表中的一波缓存页被刷入了磁盘,那么这些缓存页也会从flush链表和lru链表中移除,然后加入到free链表中去!