12.BufferPool之缓冲池污染

什么是MySQL缓冲池污染?

当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染。

例如,有一个数据量较大的用户表,当执行:

select * from user where name like "%shenjian%";

虽然结果集可能只有少量数据,但这类like不能命中索引,必须全表扫描,就需要访问大量的页:

(1)把页加到缓冲池(插入老生代头部);

(2)从页里读出相关的row(插入新生代头部);

(3)row里的name字段和字符串shenjian进行比较,如果符合条件,加入到结果集中;

(4)直到扫描完所有页中的所有row

如此一来,所有的数据页都会被加载到新生代的头部,但只会访问一次,真正的热数据被大量换出。

Buffer Pool的内存命中率急剧下降,磁盘压力增加,SQL语句响应变慢。

解决缓冲池污染:老生代停留时间窗口

MySQL缓冲池加入了一个“老生代停留时间窗口”的机制:

(1)假设T=老生代停留时间窗口;

(2)插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部;

(3)只有满足“被访问”并且“在老生代停留时间”大于T即数据页第一次被访问和最后一次被访问的时间间隔超过T秒,才会被放入新生代头部;

image.png

image.png

继续举例,假如批量数据扫描,有51,52,53,54,55等五个页面将要依次被访问。

image.png

如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会换出大量热数据。

image.png

加入“老生代停留时间窗口”策略后,短时间内被多次访问的页,并不会立刻插入新生代头部。而是优先淘汰老年代中短期内仅仅访问了一次的页。

image.png

而只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。

优化过后的 LRU 链表,又是如何进行数据页的存放的呢?

当从磁盘读取数据页后,会先将数据页存放到 LRU 链表冷数据区的头部,如果这些缓存页在 1 秒之后被访问,那么就将缓存页移动到热数据区的头部;如果是 1 秒之内被访问,则不会移动,缓存页仍然处于冷数据区中。

1 秒这个数值,是由参数 innodboldblocks_time 控制。

当遇到全表扫描或者预读时,如果没有空闲缓存页来存放它们,那么将会淘汰一个数据页,而此时淘汰地是冷数据区尾部的数据页。冷数据区的数据就是不经常访问的,因此这解决了误将热点数据淘汰的问题。

如果在 1 秒后,因全表扫描和预读机制额外加载进来的缓存页,仍然没有人访问,那么它们会一直待在冷数据区,当再需要淘汰数据时,首先淘汰的就是这一部分数据。

至此,基于冷热分离优化后的 LRU 链表,完美解决了直接使用 LRU 链表带来的问题。

如果是扫表,比如查询记录id>=10的记录,id=10的记录位于X页的第一条,假如该页面有1000条数据。

那么X页可能会被频繁访问1000次,但是X页上的所有数据都是在1秒内被访问的。

LRU算法执行流程

在InnoDB实现上,按照5:3的比例把整个LRU链表分成了young区域和old区域。

图中LRU_old指向的就是old区域的第一个位置,是整个链表的5/8处。

也就是说靠近链表头部的5/8是young区域,靠近链表尾部的3/8是old区域。

改进后的LRU算法执行流程变成了下面这样。

image.png

1.要访问数据页P3,由于P3在young区域,因此和优化前的LRU算法一样,将其移到链表头部,变成状态2。

2.之后要访问一个新的不存在于当前链表的数据页,这时候依然是淘汰掉数据页Pm,但是新插入的数据页Px,是放在LRU_old处。

3.处于old区域的数据页,每次被访问的时候都要做下面这个判断:

  • 若这个数据页在LRU链表中存在的时间超过了1秒,就把它移动到链表头部(新生代)
  • 如果这个数据页在LRU链表中存在的时间短于1秒,位置保持不变。1秒这个时间,是由参数innodboldblocks_time控制的。其默认值是1000,单位毫秒。我们生产也是1000。

这个策略,就是为了处理类似全表扫描的操作量身定制的。

以刚刚的扫表为例,我们看看改进后的LRU算法的操作逻辑:

1.扫描过程中,需要新插入的数据页,都被放到old区域;

2.一个数据页里面有多条记录,这个数据页会被多次访问到,但由于是顺序扫描,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过1秒,因此还是会被保留在old区域;

3.再继续扫描后续的数据,之前的这个数据页之后也不会再被访问到,于是始终没有机会移到链表头部(也就是young区域),很快就会被淘汰出去。

可以看到,这个策略最大的收益,就是在扫描这个大表的过程中,虽然也用到了Buffer Pool,但是对young区域完全没有影响,从而保证了Buffer Pool响应正常业务的查询命中率。

LRU 链表的极致优化

实际上,MySQL 在冷热分离的基础上还做了一层优化。

当一个缓存页处于热数据区域的时候,我们去访问这个缓存页,这个时候我们真的有必要把它移动到热点数据区域的头部吗?

从代码的角度来看,将链表中的数据移动到头部,实际上就是修改元素的指针指向,这个操作是非常快的。但是为了安全起见,在修改链表的时候,我们需要对链表加上锁,否则容易出现并发问题。

当并发量大的时候,因为要加锁,会存在锁竞争,每次移动显然效率就会下降。

因此 MySQL 针对这一点又做了优化,如果一个缓存页处于热数据区域,且在热数据区域的前 1/4 区域(注意是热数据区域的 1/4,不是整个链表的 1/4),那么当访问这个缓存页的时候,就不用把它移动到热数据区域的头部;如果缓存页处于热数据的后 3/4 区域,那么当访问这个缓存页的时候,会把它移动到热数据区域的头部。

在LRU链表的冷数据区域中的都是什么样的数据呢?

其实大家脑筋一转就知道了,大部分应该都是预读加载进来的缓存页,加载进来1s之后都没人访问的,然后包括全表扫描或者一些大的查询语句,加载一堆数据到缓存页,结果都是1s之内访问了一下,后续就不再访问这些表的数据了。类似这些数据,统统都会放在冷数据区域里。

如果一个缓存页在冷数据区域的尾巴上,已经超过1s了,此时这个缓存页被访问了一下,那么他此时会移动到冷数据区域的链表头部吗?注意,是冷数据区域的链表头部!

冷区域的缓存页不管在哪个位置,超过1s后被访问都会被放到热数据区域的头部,除非是热数据区域的前1/4被访问不会被移动到热区域的头部。只有后3/4热数据区域的数据页被访问才会移动到热数据区域的头部。

参考:https://blog.csdn.net/qq_34436819/article/details/106815706

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值