绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
LRU链表管理(2)—Buffer Pool(五十五)
2023-02-06 09:40:22

前面说了buffer pool的重要性,每次查询数据并不是I/O从磁盘获取的,而是吧磁盘上的数据刷新到buffer pool里,里面组成有缓存页和控制块,缓存页可以用innoDB_buffer_pool_size设置,控制块的内存是单独存储的。分为free链表和flush 链表,mysql数据库启动的时候,free链表里面存储的是申请的空闲缓存页。如果修改了缓存页,导致和磁盘上的数据不一致的脏数据,所以这时候flush就有 用处了,每次隔一段时间吧flush 链表的数据更新到磁盘上,并不是吧所有buffer pool的数据更新上。

LRU链表管理

Buffer pool的内存当然是有限的,当内存不够怎么办呢,当然是吧时间旧的一些数据从内存 释放,吧查询的新数据刷新到缓存页。

简单的LRU链表

当我们buffer pool里不在有空闲的缓存页时,就需要释放写内存,吧近很少使用的淘汰掉。那我们怎么知道哪些数据是很少使用的呢,这时候我们就有LRU链表(lasted recently used):

当我们在buffer pool没发现表空间id+页号,如果没有查询到,则直接把数据页缓存到缓存页,并且吧这个缓存页的控制块放在lru链表前面。

如果该lru链表已有,则直接把这个数据移到前面。

所以如果要释放内存,只要删除LRU尾部的数据就好了。

划分区域的LRU链表

但上面的简单lru存在问题,存在两个尴尬的情况:

情况一:innoDB提供了一个贴心的服务,预读(read ahead)。预读就是innoDB会根据当前执行的请求来判断之后可能会读取的数据,吧他们预先加载到buffer pool。预读又细分两种:

线性预读:mysql提供了一个系统变量innodb_read_ahead_threshold,他的默认值是56,如果顺序访问某个区超过了这个系统变量值,就会触发一次异步读取下一个区中的全部页面到缓存页中,这次运行并不会影响数据的返回,因为他是异步运行的。

随机预读:如果buffer pool已经缓存了某个区连续13个页面,但不是顺序读取的,所以没有触发线性预读,页面数据超过了系统变量innoDB_random_read_ahead这个值,则也会触发随机预读,但这个值默认是off,除非通过set global把他开启成on。

预读本来是好事,但如果数据太大,吧下一个区整个页都放入缓存页,而这些数据又没用到,导致吧lru尾部的数据从内存释放,那反而弄巧成拙。

情况二:有的情况可能没有建立索引,而写一些全表扫描的查询语句,这时候数据量太多,每次查询都给缓存页换一次血,每次都淘汰数据,再从磁盘中I/O刷新数据出来,显然和直接访问磁盘没什么区别。

总结:因为预读的原因,加载到buffer pool的数据可能不会被用到,全表扫描数据量太大的情况,可能会把使用高的数据从缓存页内存释放。


所以为了解决这个情况,所以把innoDB吧lru链表分成两截,Lru的链表又分为热数据和冷数据:

热数据(young区域):一部分使用非常高的频率。

冷数据(old区域):使用不是很高频率的数据。

那我们吧lru链表分成两半,那什么是yong区域数据,什么是old区域数据呢,随着程序的运行,这些数据都可能发生变化,那截取的比例怎么看呢,

mysql> show variables like 'innodb_old_blocks_pct'; 
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.03 sec)

从结果可以看到,old区域在lru链表占比是百分之37,这个比例是默认的,我们可以用

innodb_old_blocks_pct = 40

设置,但这个设置是全局变量,一修改,全部都会用到,对所有客户端有影响,所以我们可以这样设置

SET GLOBAL innodb_old_blocks_pct = 40;

有了young区域和old区域的设置之后,我们可以针对上面两个情况进行优化:

情况一:预读但不怎么使用的数据优化,mysql规定初次加载到缓存页的数据,先放入old区域,如果后续不继续访问,则会满满从old区域淘汰。

情况二:如果查询数据太多,导致频繁的刷新磁盘数据到缓存页,全表大量访问,如果全表扫描的数据放在old区域,但后续继续访问,导致他大量的数据放入young区域,还是会出现吧其他热点数据淘汰,所以这时候引入一个新的系统变量,innodb_old_blocks_time,

mysql> show variables like 'innodb_old_blocks_time';
 
+------------------------+-------+
 
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |
 
+------------------------+-------+
 
1 row in set (0.00 sec)

如果全表访问次和后一次的间隔在1s内,则不会放入young区域,意味着同一个页面全表查询不会超过1s间隔,如果吧当前字段设置成0,则代表直接把数据从old区域存入young区域。

综上所述,为了解决young区域和old区域在lru链表的顺利运行,使得预读机制和全表扫描导致缓存命中率大幅度降低问题得到解决,用innoDB_old_block_time有效解决。

更进一步优化lru链表

其实这样每次吧热点数据放到young区域, 对节点一直操作也不太好,毕竟young区域都是热点数据,经常访问。所以优化为只有在young区域后半1/4的区域,当再次访问,才会移动到前面,其他区域依然保持不变

(随机预读的触发13个数据页,要求其实就是这13个数据页必须在young区域的前面1/4处才触发)

其实这些本质都是为了一个目的:提高buffer pool缓存页的命中率。


文章来源:知乎平台 原文地址:https://zhuanlan.zhihu.com/p/416988735

分享好友

分享这个小栈给你的朋友们,一起进步吧。

MySQL干货资料
创建时间:2020-05-06 14:18:32
每天都有干货输出哦
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

技术专家

查看更多
  • 飘絮絮絮丶
    专家
戳我,来吐槽~