LRU链表管理(2)—Buffer Pool(五十五)

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

磁盘&CPU调节(1)—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缓存页的命中率。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端从入门到精通

你的鼓励是我最大的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值