细说InnoDB缓冲池 buffer pool(free、flush、lru) 2021-12-12 21:57:42 小道仙 126阅读 2评论
视频地址 https://www.bilibili.com/video/BV1C3411t7WL
Table of Contents
一、开篇
在InnoDB引擎中对数据库增删改查,都是先从磁盘中把数据加载到内存,然后在内存中进行相关操作,我们把这块的内存称之为 buffer pool
(缓冲池)
既然这是内存中的一块区域,那么它就一定有大小(默认是128M),如果你有一个16G的数据库服务器,你安装好MySQL,那你的缓冲池的大小就是128M,这时候肯定很影响你的操作,因为它太小了,所以我们应该根据当前条件来对这个参数进行修改:innodb_buffer_pool_size
说到数据库,大家脑海里肯定是库、表、行、字段
,这其实没错毕竟我们对它就是这样操作的,这其实也错,因为我们知道数据是存在硬盘中的,硬盘怎么会有这些个逻辑数据呢?肯定是都存储的物理数据。
在硬盘中,MySQL把16KB作为一个单位,我们把这个叫做数据页
,一页里面可以存储很多的数据,页之间是有双向指针进行关联的。
从硬盘中读取数据到缓冲池也是以页为单位的,在缓冲池中有一个叫做缓存页
的东西,它也是16KB大小,每读取一个数据页就把它放在一个空闲的缓存页里面。
除了这个缓存页之外还有一个叫做元数据
的东西,你可以这样理解这个每个元数据和每个缓存页一一对应,它相当于是缓存页的头,它里面存储了缓存页的基础信息,下面你就会明白它的作用了。
好了,到这里我们就知道了buffer pool里面有缓存页(16KB),元数据,那么你的脑海里应该浮现这样的图
buffer pool默认的大小128M,是用来存储数据的,也就是缓存页,但同时也还要存储元数据,所以实际上它的大小会比128M要大一些。
二、free链表
上面说到,我们是把硬盘中的数据页一片片加载到buffer pool的缓存页里面去,那这里就存在一个问题:怎么知道哪些数据页是空的呢?我们不可能去覆盖别的数据对不对?
其实这里维护了一个free链表
,它是由空闲的元数据组成的(前面我们说了这个元数据和缓存页是一一对应的,所以一个空闲元数据就对应一个空缓存页),这样我们就很容易的知道那些缓存页是空的、还有多少空的缓存页。
这个基本结点是单独的空间,它里面就存储了链表长度等其它的数据。
如果有新的数据页要加载到缓存中,就从free链表里面取出一个元数据找到对应的缓存页,然后放进去,在把这个元数据从free链表里面剔除。
可以理解成你在Java中的链表,伪代码实现 head.next = head.next.next
三、flush 链表
上面的free链表是标示空闲的缓存页,而我们的修改数据也是在缓存页里面操作的,这些数据并未存储到磁盘中去,毫无疑问肯定是要记录哪些缓存页是已经修改了的,不然系统怎么把它们刷进磁盘呢?而这个记录的地方也就是 flush链表
flush链表也是和上面free链表一样:基于元数据组成的。当有一个缓存页被修改之后就把它加入到flush链表里面去。
注:可能有人有疑问为啥这个元数据可以组成free链表又能组成flush链表呢?其实熟悉链表结构的朋友都知道,链表只是一个指向的问题而已,我们可以在元数据里面定义N个,next、front,指针去指向就好了。
因为flush链表和free链表结构基本上是一样的,所以就不花了,但是要理解不是单独的两个链表,而是两种指向。
有了flush链表,当要把数据刷回磁盘的时候就容易多了,具体何时刷入下面再说。
四、LRU 链表
这里我们还有一个问题,我们的数据都是存储在缓存中的,而缓存大小是有限的,有些数据很可能就是查询一次就不再使用了,如果让它一直存储在缓存中那肯定是不合适的。所以这里我们引入一个新的链表 LRU链表
(Least Recently Used)最近最少使用的意思,也就是我们可以使用这个链表来维护热点和非热点数据。
注:这个链表的节点也是基于元数据来组成的,但是和上述两个链表还是有些差异。
何为热点数据,这其实是一个定义问题。当然按照顺序排序把最近访问的数据放到链表头部,那这样的话前面的数据就是热点数据。
但是MySQL有两个操作会让上面这种方式产生漏洞,全表扫描
、预读
全表扫描
这很好理解,比如你写一个不带任何条件的查询SQL: SELECT * FTOM t_user
,这会加载很多的数据页到缓存中,但实际上很多数据只用一次就不再使用了。
预读
在InnoDB引擎下,当你一次读取超过56页数据的时候就会触发预读,也就是在InnoDB看来,如果你一下次读取了56页数据,那么你大概率也是要读取57、58…
这个56是来自默认的配置,当然你页可以修改它的配置文件 innodb_read_ahead_threshold
冷热区域
为了解决上面的这个“漏洞”,真实的LRU链表是分冷热区域的,当数据页从磁盘加载到内存的时候,会把它放在冷区域的头部,当超过1s后再次去访问这个数据的时候才会把它移动到热区域。
- innodb_old_blocks_pct 冷区域的大小默认是 37%
- innodb_old_blocks_time 多少毫秒之后访问冷数据,会加载到热区域 默认 1000
按照我们的理解如果超过1s后再访问冷区域的数据就把它移动到热区域,这个是没问题的。
但如果我们访问热区域的数据,是不是每次访问就把它移动到热区域的头部呢?这样看似合理,实则不合理,你想想频繁的移动也是需要消耗性能的,所以只有在访问热区域的后75%的数据才会移动到热区域的头部。
五、数据回盘
buffer pool是内存的一块区域,它总会有满的时候,如果它满了那怎么办呢?其实通过上面的几个链表我们已经知道了,如果满了,可以把LRU链表的冷区域的数据进行刷回磁盘操作就好了。
另外一个问题就是flush链表里面的脏页何时被刷回磁盘呢?它有两个操作,一个是上面的磁盘空间不足的时候会把LRU链表里面的冷数据刷回磁盘,这里面也包含了一些flush链表里面的数据,一个是后台有一个IO线程会不定期的把flush链表里面的脏页刷回磁盘。
注:free、flush、lru 都是指的相同的数据,只不过是按照不同的指向组成了不同的链表而已。
转载:https://www.xdx97.com/article/919709627669544960 标签:缓存,buffer,free,链表,InnoDB,flush,数据 From: https://www.cnblogs.com/wugh8726254/p/16822756.html