在上一篇文章中,介绍了LRU算法在Redis之中的应用,本篇继续给各位道友介绍在Mysql的InnobDB引擎中,是如何使用LRU算法的。html
首先来介绍下InnoDB的缓冲池,缓冲池简单来讲就是一块内存区域,该区域内缓存着InnoDB访问存储在磁盘的数据和索引信息。缓冲池有两个做用,一是提升了大容量读取操做的效率,二是提升了缓存管理的效率。调配缓存池参数,使得常常访问的参数可以保留在缓存池中是一个很重要的Mysql优化手段。mysql
一个InnoDB缓存池的内存结构图以下图所示:算法
图源自《Mysql技术内幕:InnoDB存储引擎》sql
咱们能够经过SHOW ENGINE INNODB STATUS命令来查看缓存池在InnoDB引擎中的表现:shell
----------------------
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。dom
说到缓存,必须有缓存刷新机制,即剔除缓存中的脏页(已经被修改,可是并未刷入磁盘中的数据页)。异步
在5.7以上的版本中,InnoDB会启动默认四个线程并发的来执行缓存池中脏页的清除。脏页的清除有两种模式:
innodb_max_dirty_pages_pct_lwm
(低水平线默认为25%)时,启动普通模式将脏页刷新到磁盘中。innodb_max_dirty_pages_pct
(默认为75%)时,启动更快的刷新模式,尽快的将脏页刷新到磁盘当中。InnoDB的缓存池不只是被动地缓存,并且会异步地预先从磁盘中读取数据页,有两种方式:
线性:根据缓存池的访问数据的顺序来预读,当读取某一区(Extend)中的页(Page)的数据超过innodb_read_ahead_threshold
时,则将该区中剩余的全部页都加载到缓存池中。
随机:根据缓存池中的已有页面进行预读,而无论他们的顺序,当发现缓存池中某一区内页的数量超过了innodb_random_read_ahead
,则将改区中剩余的全部页都加载到缓存池中。
在了解了InnoDB的缓存池概念后,咱们来看看背后支持缓存池工做的算法。
当咱们使用朴素的LRU算法时,会发现若是有批量的操做时,会打乱缓存数据,大大下降了缓存命中率。而在Mysql当中会有大量的预读及全表扫描的操做,为了使得真真的热数据留在内存中,InnoDB缓存池采用了一种变种的LRU算法,有些像我在这篇文章中写到的LRU-K算法。
新进入缓存池的页并不会直接进入LRU链表的头部,而是插入到距离链表尾3/8的位置(能够由innodb_old_blocks_pct参数进行配置),咱们将距离链表尾3/8以上的位置称为新子列表
,如下的位置称为旧子列表
,数据在链表中自底而上称为变年轻
,反之称为变老
。下图是一个示意图:
变年轻
变年轻分为两种状况,第一种是来源于用户的操做而须要读取页面,此时会直接使该页直接移至新子列表链表头部。第二种是来源于数据库内部的预读操做,则在距离插入innodb_old_blocks_time(默认为1000ms)的时间内,即便访问了该页,该页也不会别移到LRU链表的头部。
也就是说,若是是来源于用户的操做,则最起码须要两次操做才能变年轻。而若是是预读操做,则须要加上一个等待期限。
变老
随着链表数据的替换和访问,整个列表中的数据会天然的变老。最终最老的页面会从尾部逐出。
本文介绍了Mysql的InnoDB引擎的缓存池的概念,及其对于LRU算法的改造。介绍了另外一种解决LRU列表被污染的解决方案。