LRU算法及其优化策略——Mysql篇

InnoDB缓冲池
缓存池简介及内存结构
首先来介绍下InnoDB的缓冲池,缓冲池简单来说就是一块内存区域,该区域内缓存着InnoDB访问存储在磁盘的数据和索引信息。缓冲池有两个作用,一是提高了大容量读取操作的效率,二是提高了缓存管理的效率。调配缓存池参数,使得经常访问的参数能够保留在缓存池中是一个很重要的Mysql优化手段。
一个InnoDB缓存池的内存结构图如下图所示:

图源自《Mysql技术内幕:InnoDB存储引擎》
缓存池状态
我们可以通过SHOW ENGINE INNODB STATUS命令来查看缓存池在InnoDB引擎中的表现:
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 6593445888; // 为缓冲池分配的总内存(字节)
Dictionary memory allocated 7687783 // 为InnoDB数据字典分配的总内存(字节)
Buffer pool size 393208 // 分配给缓冲池的页面总大小(页)
Free buffers 352642 // 缓冲池空闲列表的页面总大小(页)
Database pages 40485 // 缓冲池LRU列表的页面总大小。(页)
Old database pages 14967 // 缓冲池旧LRU子列表的页面总大小(页)
Modified db pages 4 // 缓冲池中当前修改的页面数。
Pending reads 0 // 等待读入缓冲池的缓冲池页面数。
Pending writes: LRU 0, flush list 0, single page 0 // 从LRU列表的底部开始写入的缓冲池中的旧脏页数。 // 检查点期间要刷新的缓冲池页面数。
// 缓冲池中暂挂的独立页面写入数。
Pages made young 5, not young 0 // 缓冲池LRU列表中变年轻的页面总数
// 缓冲池LRU列表中未设置为年轻的页面总数
...
完整的缓存池状态信息可以在这里找到:缓存池状态信息
缓存池的数量和大小
为了避免多个线程读写缓存池引起的并发冲突,InnoDB可以配置多个缓存池,由参数innodb_buffer_pool_instances
指定,内部使用散列表进行分配和管理。
通常来说,当缓存池的大小越大,则Mysql表现的越像一个内存数据库。我们可以在启动时或者运行时通过innodb_buffer_pool_size
参数动态地调整缓存池的大小,需要注意的innodb_buffer_pool_size
的大小会自动的调整为InnoDB缓存池块innodb-buffer-pool-chunk-size
(默认为128M)的整倍数。
为避免潜在的性能问题,缓存池大小/缓存池块大小(
innodb_buffer_pool_size
/innodb_buffer_pool_chunk_size
)的数量不应超过1000。
缓存池的刷新
说到缓存,必须有缓存刷新机制,即剔除缓存中的脏页(已经被修改,但是并未刷入磁盘中的数据页)。
在5.7以上的版本中,InnoDB会启动默认四个线程并发的来执行缓存池中脏页的清除。脏页的清除有两种模式:
- 普通模式,当缓存池中的脏页比例超过
innodb_max_dirty_pages_pct_lwm
(低水平线默认为25%)时,启动普通模式将脏页刷新到磁盘中。 - aggressively flushes(激进模式?),当缓存池中的脏页比例超过
innodb_max_dirty_pages_pct
(默认为75%)时,启动更快的刷新模式,尽快的将脏页刷新到磁盘当中。
缓存池的预读(Prefetching )
InnoDB的缓存池不仅是被动地缓存,而且会异步地预先从磁盘中读取数据页,有两种方式:
-
线性:根据缓存池的访问数据的顺序来预读,当读取某一区(Extend)中的页(Page)的数据超过
innodb_read_ahead_threshold
时,则将该区中剩余的所有页都加载到缓存池中。 -
随机:根据缓存池中的已有页面进行预读,而不管他们的顺序,当发现缓存池中某一区内页的数量超过了
innodb_random_read_ahead
,则将改区中剩余的所有页都加载到缓存池中。
缓存池LRU算法
在了解了InnoDB的缓存池概念后,我们来看看背后支持缓存池工作的算法。
新进入缓存池的页并不会直接进入LRU链表的头部,而是插入到距离链表尾3/8的位置(可以由innodb_old_blocks_pct参数进行配置),我们将距离链表尾3/8以上的位置称为新子列表
,以下的位置称为旧子列表
,数据在链表中自底而上称为变年轻
,反之称为变老
。下图是一个示意图:

-
变年轻
变年轻分为两种情况,第一种是来源于用户的操作而需要读取页面,此时会直接使该页直接移至新子列表链表头部。第二种是来源于数据库内部的预读操作,则在距离插入innodb_old_blocks_time(默认为1000ms)的时间内,即使访问了该页,该页也不会别移到LRU链表的头部。
也就是说,如果是来源于用户的操作,则最起码需要两次操作才能变年轻。而如果是预读操作,则需要加上一个等待期限。
-
变老
随着链表数据的替换和访问,整个列表中的数据会自然的变老。最终最老的页面会从尾部逐出。
总结
本文介绍了Mysql的InnoDB引擎的缓存池的概念,及其对于LRU算法的改造。介绍了另一种解决LRU列表被污染的解决方案。
转载自:https://juejin.cn/post/6844904051012796424