MySQL 的 BufferPool 是个啥?

228 阅读7分钟

BufferPool 概述

望词知义,BufferPool 即缓冲池。缓冲池作用无外乎减少DB/IO的查询压力,加快查询速度。MySQL InnoDB引擎中,数据基于磁盘进行存储,并按照页的方式进行管理。BufferPool的出现就是为了缓存CPU和磁盘之间的速度,提升数据库性能。缓冲池其实类似于Redis数据库,基于内存实现数据缓冲。当进行select操作时,如果缓冲池中没有相应的数据,则需要进行一次随机磁盘io查询,把相应的数据加载到缓冲池BufferPool中,如果下次还是访问同样的数据,直接从缓冲池中拿数据即可,不需要再次从磁盘中拿数据。当进行update/insert等操作时,会更新 BufferPool 的数据。当然缓冲池的数据不会立马刷新到磁盘中,而是通过Checkpoint机制刷新到磁盘中,将随机io变为顺序io,提升数据库性能。

BufferPool除了用来存储索引页和数据页,还存储了undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息、数据字段等。

Free链表

MySQL 为 BufferPool 设计了一个双向链表 free 链表,这个 free 链表的作用就是用来保存空闲缓存页的描述块

Flush链表

MySql 在执行增删改的时候会一直将数据以数据页的形式加载到 BufferPool 的缓存页中,增删改的操作都是在内存中执行的,然后会有一个后台的线程数将脏数据刷新到磁盘中,但是后台的线程肯定是需要知道应该刷新哪些啊。

针对这个问题,MySQL设计出了 Flush 链表,他的作用就是记录被修改过的脏数据所在的缓存页对应的描述数据。如果内存中的数据和数据库和数据库中的数据不一样,那这些数据我们就称之为脏数据,脏数据之所以叫脏数据,本质上就是被缓存到缓存池中的数据被修改了,但是还没有刷新到磁盘中。

同样的这些已经被修改了的数据所在的缓存页的描述数据会被维护到 Flush 中,所以 Flush 中维护的是一些脏数据数据描述(准确地说是脏数据的所在的缓存页的数据描述)

另外,当某个脏数据页页被刷新到磁盘后,其空间就腾出来了,然后又会跑到 Free 链表中了。

LRU链表

通常来说,数据库缓冲池通过 LRU(最近最少使用算法)来管理(和Redis有点像),最频繁访问的放在链表的最前面,最少使用的放在链表的末尾。当LRU列表放不下更多的数据时,就会把链表的末尾数据释放掉。for example,初始lru列表如下:

1 -> 2 -> 3 -> 4 -> 5

当访问数据2(在列表中)时,列表如下:

2 -> 1 -> 3 -> 4 -> 5

当访问数据5(不在列表中)时,列表如下:

6 -> 2 -> 1 -> 3 -> 4

把数据5淘汰掉。

但是LRU算法会存在一个问题:当我们运行如下sql时,

SELECT * FROM table_name

LRU算法会把当前存在BufferPOol中的所有数据淘汰,将上述sql查询的结构放到列表中。而上述查询全量数据sql语句频率很低。所以当一个查询频率低的sql语句的查询结果放到LRU中,会造成查询频率高的数据淘汰,当下次查询时,又需要把数据从磁盘中加载到BufferPool中。为此,我们需要区分那些是数据查询频率高,那些查询频率低。此外,MySQL本身具有预读机制,基于局部性原理,在加载某数据页的数据时,会把相邻数据页的数据加载到BufferPool中。

那MySQL是如何解决BufferPool中数据频繁换进换出的问题呢?

答案就是将LRU列表一分为二:查询频率高的和查询频率低的,基于冷热数据分离的LRU链表。结果如下所示,链表前一部分是热数据(New)区,后半部分是冷数据(Old)区。

| 6 -> 2 -> 1 -> 3 -> 4 | | 7 -> 8 -> 9 -> 10 -> 14 |

数据从磁盘加载到BufferPool中,首先会把数据当道Old部分头部,在一定时间范围内,如果该数据被访问,那么这个数据页会被放转移到New链表的头部。之所以在一定时间范围内,是为了避免出现数据刚被加载到Old区,又被访问了一次,然后放入New区头部,之后再也没有访问过。

经过上面的分析,冷热分离LRU基本解决了之前存在的频繁换入换出问题,但是还有一个非常重要的问题:New区数据往往是热点数据,热点数据的访问会经常造成头节点的更改,数据页需要频繁的移动,这也会带来一定的性能问题,为此,MySQL有如下规则解决上述问题:

如果被访问的数据所在的缓存页在New区的前25%,那么该缓存页对应的描述数据是不会被转移到热数据链表的头部的,只有当被访问的缓存页对应的描述数据在New区链表的后75%,该缓存页的描述数据才会被转移到热数据链表的头部。

例如,当热区存在100个数据页时,当访问0-24个数据页时,数据页不会转移到链表头部,只有访问25-99的数据时,数据页才会放到New区链表头。

BufferPool参数含义

show engine innodb status;

----------------------
 Buffer Pool  AND MEMORY
----------------------
--  Buffer Pool 的最终大小
Total memory allocated
--  Buffer Pool 一共有多少个缓存页
 Buffer Pool  size
-- free 链表中一共有多少个缓存也是可以使用的
Free buffers        
-- lru链表中一共有多少个缓存页
Database pages 
-- lru链表链表中的冷数据区一共有多少个缓存页
Old database pages  
-- flush链表中的缓存页的数量
Modified db pages     
-- 等待从磁盘上加载进来的缓存页的数量
Pending reads 
-- 即将从lru链表中刷入磁盘的数量,flush链表中即将刷入磁盘的缓存页的数量
Pending writes: LRU 0, flush list 0, single page 0
-- lru链表的冷数据区的缓存页被访问之后转移到热数据区的缓存页的数量,以及冷数据区里1s之内被访问但是没有进入到热数据区的缓存页的数量
Pages made young 260368814, not young 0
-- 每秒从冷数据转移到热数据区的缓存页的数量,以及每秒在冷数据区被访问但是没有进入热数据区的缓存页的数量
332.69 youngs/s, 0.00 non-youngs/s
-- 已经读取创建和写入的缓存页的数量,以及每秒读取、创建和写入的缓存页的数量
Pages read 249280313, created 1075315, written 32924991 359.96 reads/s, 0.02 creates/s, 0.23 writes/s
-- 表示1000次访问中,有多少次是命中了BufferPool缓存中的缓存页,以及每1000次访问有多少数据从冷数据区转移到热数据区,以及没有转移的缓存页的数量
 Buffer Pool  hit rate 867 / 1000, young-making rate 123 / 1000 not 0 / 1000
-- lru链表中缓存页的数量
LRU len: 8190
-- 最近50s读取磁盘页的总数,cur[0]表示现在正在读取的磁盘页的总数
I/O sum[5198]:cur[0],