Innodb的LRU列表解析

Innodb为了加快对磁盘中数据的操作,在操作磁盘上的数据时,会先把数据存放到一块名为Buffer Pool的内存缓冲池中(缓冲池以页为单位进行缓存,页大小为16K)。

由于受到机器限制,内存的大小远小于磁盘的大小,因此需要一种机制来淘汰非热点数据,保证内存中存在的数据是较为频繁访问的数据。

其中LRU是这种管理场景下最常用的算法,LRU算法的思想为:

  1. 新数据插入到链表头部;
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  3. 当链表满的时候,将链表尾部的数据丢弃。

这样就能够保证热点数据位于LRU的头部,而非热点数据随着其他数据的加入最终在链表尾部被丢弃。在实现该算法时一般会针对程序的特性对算法进行修改。

那么在实现LRU的过程中Innodb有以下需要注意的地方:

  1. 当数据库针对一张大表进行全表查询的时候,按照朴素的LRU算法,所有的数据都会被放入LRU链表中。从而将其他查询语句留下的高频数据挤到LRU尾部并丢弃。

  2. LRU中为了避免对磁盘的多次读取而实现了预读机制,预读机制会额外读取一些可能会被读取的页。

    • 线性预读:当一个区中有连续56个页面(56为默认值,一个区64页)被加载到BufferPool中,会将这个区中的所有页面都加载到BufferPool中。
    • 随机预读:当一个区中随机13个页面(13为默认值)被加载到BufferPool中,会将这个区中所有页面都加载到BufferPool中。随机预读默认是关闭,由变量innodb_random_read_ahead控制。

    按照朴素的LRU算法,这些数据都会被放入LRU链表中。从而将其他查询语句留下的高频数据挤到LRU尾部并丢弃。

如果以上产生的数据仅仅在这次查询中需要,并不是活跃的热点数据,那就会导致缓存命中率下降。

为了解决以上提到的缺点,InndoDB使用了以下两个策略:

  1. 将LRU链表划分为old区与young区,young区位于链表的头部,负责存放经常被访问的高频数据,而old区位于链表尾部,负责存放不经常访问的热点数据。而两个区的交汇点称为midpoint。Innodb中使用innodb_old_blocks_pct来划分两个区,该参数表示old区的比例。刚进来的数据存放在midpoint位置的old区域。
  2. 划分区域后需要一个策略来判断数据何时从old区进入young区。Innodb定义了参数innodb_old_blocks_time,单位为毫秒。当old区中某个页的最后查询时间跟第一次查询时间差超过该参数,该页就会从old区进入young区。这个策略避免了只查询一次或只在短暂的时间查询多次的数据进入young区,防止热数据被刷出内存。

有了以上策略,就能清楚知道Innodb如何使用LRU列表管理读取的页:

  1. 扫描过程中,若需要新插入数据页,首先从Free列表中查询是否有可用的空闲页。

    • 有可用空闲页:将该页从Free列表中删除,放入到LRU列表中。
    • 无可用空闲页:淘汰LRU列表末尾的页,将该内存空间分配给新的页。

    将新的页插入到midpoint位置的old区域。

  2. 扫描过程中,若需要更新数据页,根据参数innodb_old_blocks_time判断是否将old区数据更新到young区。

    • 最后查询时间跟第一次查询时间差超过该参数,则说明多次访问该数据,则将其从old区加入young区,该操作称为page made young。
    • 最后查询时间跟第一次查询时间差没有超过该参数,比如只查询了一次,或者一次查询该页的多个数据。这些操作的时间极短且频率低。不会将其加入young区,该操作称为page not made young。

通过命令SHOW ENGINE INNODB STATUS可以观察LRU列表的使用情况。

Databases pages:表示LRU列表中页的数量。

pages made young:表示LRU列表中页移动到young区的次数。

not young:表示LRU列表中页没有移动到young区的次数。

youngs/s 、non-youngs/s:表示了每秒这两类操作的次数。

Buffer pool hit rate:表示缓冲池的命中率。

你可能感兴趣的:(mysql)