MySQL Buffer Pool篇

原理

对于innoDB引擎来说,页是内存和磁盘交互的最小单位,也就是说,写入和读取都是以页为单位的。如果每次都为了读取或者修改一条数据而以页为单位发生内存和磁盘的交互,这种性能消耗是完全不可取的。

那如果把最小单位改成比页小呢?

假设找到了一个合适的最小单位来替换页,但在绝大多数时候,要修改的多条数据都是无序的,比如要改张三的年龄和李四的性别,这两条数据大概率不是连续的,也就意味着大量的随机IO,这种执行方法的效率也是极低的。

根据计算机的局部性原理,当读取一条数据时,接下来很大概率会读取与它相临的记录。如果将整个页都加载到内存中,下次读取相临记录就可以直接从内存中读取,减少磁盘IO。以相对廉价的内存空间换取CPU性能,针对这种场景,这部分廉价的内存空间在innoDB引擎中就是Buffer Pool。

结构

Buffer Pool是用来缓存数据页的,自然也是以页为单位组成的,可以用缓冲页来称呼。

为了管理这些缓冲页,每个缓冲页都有一个对应的控制块,记录了缓冲页的基本信息以及和数据页的对应关系。

(这里大胆猜测一下,每种页都对应着一种数据的存储格式,这些页都会有自己对应的一个记录了页相关信息的结构以方便管理,并通过这种结构根据页的内存使用情况将页与页之间串联和分隔开来,方便了后续内存空间的分配等操作。然后为了管理串联起来的链表,又有一个对应的结构来记录链表的基本信息以方便快速定位等操作)。

所以Buffer Pool的结构,实质上就是若干对缓冲页和控制块,以及一些未被利用的内存碎片组成。

Free链表

由于在控制块中记录了缓冲页的链表信息,所以所有未被使用过的缓冲页都会被串联起来,构成Free链表。每次从磁盘读取一个页面时,都会从Free链表中移除一个缓冲页来记录读取的页。

为了能定位到Free链表,Free链表会有一个对应的链表基节点,记录了链表的头尾节点和个数。

缓冲页哈希表

当我们需要读取一个页时,通过缓冲页哈希表就能立刻知道要读取的页是否已经在内存中了。在单个表空间内的页号是唯一的,所以要找到一个页,只需要知道表空间ID和页号就能立马定位了。通过建立一个哈希表,key是表空间ID+页号,value就是缓冲页,这样就能立马知道要读取的页是否已经在内存中,并且可以立即操作了。

Flush链表

为了削减频繁修改带来的频繁落盘,innoDB会先在内存中修改缓冲页,这些被修改过的缓冲页也叫做脏页,然后由一个额外的线程将这些脏页刷到磁盘中去。Flush链表就是脏页的链表。

为了能定位到Flush链表,Flush链表会有一个对应的链表基节点,记录了链表的头尾节点和个数。

LRU链表

从Free链表移除出来记录数据的缓冲页,串起来就构成了LRU链表。与上面两种链表不同的是,上面两种链表取数据都是从头部或者尾部去取页或者插入页,而LRU链表会对链表内页的顺序做调整,调整的依据便是LRU算法(最近最少使用淘汰)。因为Buffer Pool的大小相对于整个MySQL而言是杯水车薪的,如果让Buffer Pool等于MySQL的最大内存,MySQL也就变成了内存数据库。Buffer Pool的本质是为了减少磁盘IO,也就是说,Buffer Pool必须保持一个较好的缓存命中率,所以它保存的页一定是使用频率最高的页。当它内存用满了,首先淘汰的肯定得是频率最低的页。

但是LRU算法是有局限性的。如果插入的数据量大于LRU链表的最大容量,LRU链表内的数据就相当于被全部清洗了一遍,也就是说,大量短期可能不会被访问的数据将访问频率很高的数据挤掉了,缓存命中率会显著降低。而在innoDB引擎中有两种操作会给Buffer Pool一次带来大量:全表扫描和预读机制。全表扫描没什么好解释的。执行读请求时,innoDB判断是否会读其它页的,如果会的话就会异步的将这些页提前加载到Buffer Pool中,从而加速读操作,这个就是预读。

预读机制分为两种:

1.线性预读:如果顺序的访问某个区的页面数超过56,就会预读下个区的所有页到Buffer Pool中。56是默认值,可以通过innodb_read_ahead_threshold修改。

2.随机预读:如果某个区的13个连续页面被访问,本区的其它页面都会被预读到Buffer Pool中。13是默认值,可以通过innodb_random_read_ahead修改。

所以为了处理LRU算法的局限性,innoDB将LRU链表分为两部分:存储访问频率很高的young区和存储访问频率较低的old区。首次加载到Buffer Pool的页会先加到old区的头部,如果后续访问到了这个页的缓冲页,判断距离上次访问的时间间隔是否大于阈值,大于阈值的话就加入到young区的头部。young区和old区依旧遵循LRU算法,只不过在逻辑上看相当于各自独立维护了一个链表。而且由于old区的更新非常频繁,为了减少内存操作,如果访问的缓冲页在old区的前1/4,就不移动到old区的头部了。

多实例

现在我们知道,Buffer Pool的实质其实是对内部链表的各种操作。为了保证链表数据的一致性,这些操作都会对对应的链表加锁,也就会带来高并发场景下的资源竞争问题。多实例的引入,就是为了解决这个问题。

动态调整Buffer Pool属性

在MySQL5.7.5以前是不支持运行时调整Buffer Pool的大小的。如果允许的话,就意味着需要先申请一块新的内存,然后将老的Buffer Pool的数据拷贝到新的Buffer Pool中去,最后释放老的Buffer Pool的内存,开销极大。之后引入了chunk的概念来化整为零。一个Buffer Pool是由若干个chunk组成的,也就是Buffer Pool申请内存空间都是以chunk为单位去申请的。这样一来,如果运行时调整了Buffer Pool的大小,那就相对应的去累加或者减少chunk的个数就好了。至于这样带来的缺点的话,我暂时只想到一个,就是内存碎片会变多。虽然不再是一整块内存,但是本来就是链表结构,所以应该不会受什么影响。

配置

innodb_buffer_pool_size:Buffer Pool的大小

innodb_buffer_pool_instances:Buffer Pool的个数

innodb_buffer_pool_chunk_size:chunk的大小。运行时不可修改,不然这个结构的提出都没意义

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值