MySQL LRU内存淘汰机制
LRU算法简介
MySQL使用了LRU淘汰算法,LRU也就是least recently use,最近最少使用。
策略就是使用的缓存页就加到LRU链表的头部,只要修改或者查询过就会移到链表头部,最后淘汰LRU尾部的。
其实所谓的lru链表本质上就是一个双向循环链表,如下图:
LRU算法存在问题
1、空间局部性
操作系统级别的空间局部性原理:
spatial locality(空间局部性):也就是说读取一个数据,在它周围内存地址存储的数据也很有可能被读取到,于是操作系统会帮你预读一部分数据。
mysql也是存在存在预读机制的!
①、通过参数innodb_read_ahead_threshold控制,默认是56。这个参数表示如果顺序访问了一个区里的多个数据页,这里的多个就是56,就会触发预读机制,把下一个区中所有的数据页都加载到缓存页里。
②、通过参数innodb_random_read_ahead控制,默认是off。这个参数表示如果缓存了一个区的13个连续数据页,就会触发预读机制,把这个区里的页全都加载到缓存页里。
当你执行select * from xxx;时,如果表中的数据页非常多,那这些数据页就会一一将buffer pool中的经常使用的缓存页挤下去,可能留在lru链表中的全部是你不经常使用的数据。
综上你可以看到,所谓的预读机制的优势,实际上违背了lru去实现将最近最少使用的数据页刷入磁盘的设计初衷。
2、全表扫描
如果是全表扫描,会把全表都加载到buffer pool中,有可能就把LRU链表中经常访问的都挤到后面去,就有可能被淘汰。
如何优化呢?
既然有经常访问的数据,又有不常访问的数据,是不是可以在LRU链表中分区,对这两块数据分别管理。
基于冷热数据分离的LRU链表
MySQL中设计LRU链表,是将冷热数据分离,链表分为两部分,一部分是冷数据,一部分是热数据,冷热数据占比由配置项innodb_old_blocks_pct控制,默认为37,即37%是冷数据
数据分布
lru链表被Midpoint分成了new sublist和old sublist两部分。
其中new sublist大概占比5/8,old sublist占比3/8。
数据加载
数据页从磁盘加载到buffer时,先加入old sublist。
LRU列表中有一个Midpoint的位置,新读取到的数据页并不是直接放入到LRU列表的首部,而是放入到LRU列表的Midpoint位置,这个操作称之为Midpoint insertion stategy,也叫中间点插入策略。在默认配置下,该位置在LRU长度的5/8处,这也就是上面使用8个数据页的作用。
热数据加载
从old区到new区
数据页第一次被加载进BufferPool时在old区
头部。 当这个数据页在old区
,再次被访问到,会做如下判断
-
访问的时间跟第一次访问的时间间隔相差1s以上,满足这个条件,则该页将会被放入new_lru列表的头部。
也就会从old区进入到new区了。
-
这个存在时间由
innodb_old_blocks_time
控制,默认1000毫秒。
从new区到old区
随着冷数据查询变多或者热数据查询变多,相应的他们对应的两个区的大小也不一样。
不管是old区变长还是new区变长,只要每次区间长度变化时保证Midpoint始终指向5/8的位置,那么就可以保证old区占用3/8的空间。
如果new区变长,当Midpoint指针移动时,那么这时new区的空间就会部分转移到old区中。
热数据内部的移动
在热数据区域,如果一个缓存页被访问,不一定会立马移动到热数据链表的表头,因为频繁的移动链表也是有性能消耗的,因此,MySQL中设计为,如果热数据链表前25%(1/4)的缓存页被访问,他们是不会被移动的,只有在后75%(3/4)中的缓存页被访问,才会移动到表头,这样就能尽可能的减少链表中节点的移动,从而减小性能的损耗
计算过程
每一次移动会产生一个lock
两个重要参考:
freed_page_clock:Buffer Pool淘汰页数
LRU_new长度1/4
移动时机
当前freed_page_clock - 上次移动到Header时freed_page_clock>LRU_new长度1/4
说明:
- 当热数据第一加入或者是上次移动到Header时会记录一个freed_page_clock
- 再次访问到该数据的时候有一个当前的freed_page_clock
- freed_page_clock 减去 上次移动到Header时freed_page_clock ,如果大于LRU_new长度1/4,则该页移动到LRU_new的表头
冷数据刷盘
什么时候将LRU链表中的冷热数据中的缓存页刷盘
定时刷盘,MySQL会起一个后台线程,运行定时任务,每隔一定的时间就将LRU链表的冷数据区域尾部的一些缓存页刷盘,然后清空这些缓存页,并放入free链表,从LRU链表删除,从Flush链表删除