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

2023-02-06 00:00:00 数据 缓存 情况 区域 链表

前面说了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)

相关文章