MySQL-Buffer Pool

简述

MySQL中磁盘和内存的交互是以页来做单位,对记录进行操作的时候,需要把记录所在的页加载到内存中,页从磁盘到内存加载是要消耗性能的(IO代价),如果每一次对记录的操作都要进行这些的加载,那么对MySQL的整体性能来说是非常不利的,因此MySQL在把页加载到内存操作后并不会立刻释放,而是把页缓存在一个缓存区中,如果又有请求需要操作这个页中的数据,就可以省去加载的IO代价。

Buffer Pool

我们说了MySQL会把页缓存在一个缓存区中,这个缓存区就是Buffer Pool,MySQL在启动的时候会向内存申请一片连续的区域形成缓冲区,存储在这个缓冲区中的页称为缓存页,默认情况下Buffer Pool为128MB,我们可以通过配置文件进行修改,最低不能设置小于5MB。

[server]
innodb_buffer_pool_size = 268435456     //设置为256MB

Buffer Pool组成

Buffer Pool中存放着缓存页(16KB),当然还有存储控制信息的控制块,一个控制块对应一个缓存页,控制空存储像表空间编号、页号、缓存页在Buffer Pool的地址、链表节点信息等。形成如下结构:
在这里插入图片描述
我们设置的innodb_buffer_pool_size是不包括控制块的,控制块大小大概是缓存页的5%,也就是说我们图上所示结构的大小大概比我们innodb_buffer_pool_size设置的大小大5%。
至于碎片,是因为我们很可能没有办法在剩下的Buffer Pool空间放下一个缓存页与控制块,那么这个时候剩下的空间就没用了,称为碎片。

Free链表

在MySQL启动的时候,就会向内存申请一块连续的内存空间Buffer Pool,申请成功之后就会把这块内存分成等份的控制块和缓存页,此时缓存页还没有缓存实际的数据页,是空闲的,为了方便在使用的时候快速的分辨哪个缓存页是空闲或者以及被使用,MySQL会把空闲的缓存页的控制块形成一个链表,这样就可以快速的拿到空闲的缓存页,注意是控制块形成链表。
在这里插入图片描述
count记录着链表还有多少的节点,每次需要一个缓存页就可以把一个控制块从free链表移出就可以了。

如何判断页是否在缓冲区中

我们说了页会被缓存在缓冲区中,下次还有对该页的操作发生的时候就可以快速的从缓存中拿到缓存页,那么MySQL怎么知道页在不在缓存中,总不可能区遍历缓冲区吧,由于我们在磁盘定位页都是用表空间号和页号来定位的,因此我们可以把表空间号和页号作为key,控制块作为value,形成一个哈希表结构,这样就可以在O(1)的时间得到是否存在获取和对应的控制块。

Flush链表

我们对页的操作可不仅仅只有查询,还有增删改,当发生这些操作的时候,缓存页就与磁盘的数据页不同了,我们把这种缓存页称为脏页,脏页是要刷新到磁盘的,而MySQL不会每次有修改就会把脏页刷新到磁盘,为了效率会把脏页先放着,在固定的时间点才会进行脏页的刷新,那么就涉及到在之后怎么判断一个缓存页是否是脏页的问题,这个问题和判断一个缓存页是否空闲是一样,所以MySQL用了一模一样的数据结构-链表,让所有的脏页控制块形成链表。

LRU链表管理

被使用的缓存页的控制块也会形成一个链表,这个链表就是LRU链表,由于Buffer Pool的大小是有限的,当我们的缓存不断增加的时候,终有某一个时候,free链表这也没有空闲的缓存页了,这个时候就要从已经使用的缓存页中淘汰掉缓存页来得到一个空闲的缓存页。
如何确定缓存页可以被淘汰
MySQL按照最少使用的原则区淘汰缓存页,最少被使用的页会存在LRU链表的链尾。
LRU链表的链尾为最少使用的缓存页
因为按照LRU的规定,每当一个缓存页被使用的时候,就会把它移动到链表头部(简单来说),所以LRU链表的链尾为最少使用的缓存页。

划区域的LRU链表

上面说的是非常简单的情况,然而MySQL实际的情况并不会这么简单,因为MySQL会有预读的行为,也就是MySQL会预测下一个我们可能使用到的数据页进行缓存以此来提高性能,预读有两种:

  1. 随机预读:如果在一个区(包含64个页)中,有13个连续的页面都被加载到了Buffer Pool中,那么MySQL会把这个区所有的页面都加载到Buffer Pool。
  2. 线性预读:如果在一个区(包含64个页)中,有56个页被使用了,那么会被对于的下一个区加载到Buffer Pool。

居然是预读,那么代表这些页很有可能不会被用到,按照我们之前的说法会把新加载的页放到LRU链表的链表头,如果这些页没有被用到,那么我们LRU链表链头位置就缓存了很多没用的缓冲区,并且有可能导致我们那么高频使用的缓冲区被淘汰。
为了解决这个问题,设计把LRU链表进行了分区:young区域和old区域。
在这里插入图片描述
我们在加载数据页之后,并不会把把缓存页立刻放到LRU链表的头部,而是放在old区域的头部,只有满足条件之后才会把缓存页放到LRU链表的头部,已经就解决了加载进来数据页可能不会被使用却被放到LRU链表头部的问题,默认情况下old区域占整个LRU链表的37%。我们可以通过配置修改这个比例:

[server]
innodb_old_blocks_pct = 40          //old占整个LRU链表的40%

全表扫描的问题
我们前面说只有满足条件之后才会把缓存页放到LRU链表的头部,那么应该满足什么条件呢?如果只是简单判断发生了操作就把缓存页放到LRU链表的头部,那么在全表扫描的时候,一个B+数的叶子节点基本都会被使用到,但是全表扫描又是一个使用频率非常低的操作,把这些缓存页放到LRU链表的头部明显是不妥的,虽然发生了操作。
因此根据像全表扫描这些操作的特点(对页中数据行的多次操作之间发生频率非常高,而且间隔非常短),设计者规定,操作间隔如果小于1秒钟,就不会把缓存页放到LRU链表的头部,只有在间隔时间大于1秒才会把缓存页放到LRU链表的头部,这样就算全表扫描,扫描的缓存页也只会放在old区域中,我们可以通过SHOW VARIABLES LIKE ‘innodb_old_blocks_time’;来查看这个间隔时间。
在这里插入图片描述
进一步对LRU链表进行优化
我们都知道young区域的缓存页发生操作就会被移动LRU链表的头部,那么假设第2个链表节点和第1个链表节点频繁的交替发生操作,那么就会平凡发生链表移动操作,也是消耗性能的,并且两个节点本身就位于链表的前列,根本不需要这样移动,因此设计者位于LRU链表young区域前1/4的缓存页哪怕发生操作也不会移动到链表头部,减少链表的移动操作。

刷新脏页到磁盘

MySQL有专门的线程会每隔一段时间就把脏页刷新到磁盘,在后台进行可以不影响用户的使用,刷新方式有三种:

  1. BUF_FLUSH_LRU:从LRU链表链尾扫描一部分页面进行刷新,扫描的页面数量可以通过innodb_lru_scan_depth配置,扫描的时候要判断页面是否是修改过的,在进行刷新,同时刷新完修改控制块的刷新标识为没有修改过(因为LRU的控制块也会在FLUSH链表里,这个标识可以防止重复刷新)。
  2. BUF_FLUSH_LIST:从flush链表刷新一部分修改过页面到磁盘。
  3. BUF_FLUSH_SINGLE_PAGE:可能有这种情况,LRU链表最后一个页面为脏页,此时没有到发生刷新的时间点,缓冲区满了,这时可能会发生淘汰,淘汰链表尾部的那个脏页,这个时候,MySQL就不得不把这个脏页同步刷新到磁盘中,因为是同步所以降低处理用户请求的性能,这种刷新称为BUF_FLUSH_SINGLE_PAGE。

多个Buffer Pool

在多线程环境下,单一的Buffer Pool可能需要加锁处理来保证线程安全,我们可以通过配置innodb_buffer_pool_instances参数来配置Buffer Pool的数量,降低性能消耗,默认为1。

多个chunk

Buffer Pool又由多个chunk组成,每个chunk是一个连续的内存快,通过chunk MySQL实现在运行时通过的更改Buffer Pool的大小,而不用区重新申请内存区域。

查看Buffer Pool的状态信息

通过命令,可以查看Buffer Pool的状态信息:

SHOW ENGINE INNODB STATUS;

在这里插入图片描述
status列关于Buffer Pool的状态信息:

----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 8568832      //全部内存大小
Dictionary memory allocated 388900        
Buffer pool size   512                                //可以存储多少缓存页
Free buffers       0                                      //还有多少缓存页
Database pages     508                            //LRU中缓存页的数量
Old database pages 0
Modified db pages  0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 1266, created 142, written 163
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 508, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值