由于硬盘和内存的造价差异,一台主机实例的硬盘容量通常会远超于内存容量。对于数据库等应用而言,为了保证更快的查询效率,通常会将使用过的数据放在内存中进行加速读取。
数据页与索引页的LRU
数据页和索引页的目的在于缓存一部分的表数据和索引数据,其数据总量通常会超过缓冲池大小,所以缓冲池中应只缓冲那些经常使用的热点数据。InnoDB内存管理使用的是最近最少使用(Least Recently Used, LRU)算法。来淘汰最久未使用的数据
在一般的LRU算法中,当链表中的某一个数据被读取时,将会将其放置于队首。当新增数据且链表已达最大数量时,将链表尾部的数据移除,并将新增的数据置于链表首部。
InnoDB的LRU并没有使用传统的双端链表,而是做了改进,这里有两个问题:
优化预读失效
由于预读(Read-Ahead),提前把页放入了缓冲池,但最终 MySQL 并没有从页中读取数据,称为预读失效。
Read-Ahead机制
Read-Ahead用于异步预取buffer pool中的多个page的一个预测行为。
InnoDB使用两种提前预读Read-Ahead算法来提高I/O性能。
如果一个extent中的被顺序读取的page超过或者等于 innodb_read_ahead_threshold 参数变量时,Innodb将会异步的将下一个extent读取到buffer pool中,innodb_read_ahead_threshold可以设置为0-64的任何值(注:innodb中每个extent就只有64个page),默认为56。值越大,访问模式检查就越严格。
如果当同一个extent中连续的13个page在buffer pool中发现时,Innodb会将该extent中的剩余page读到buffer pool中。控制参数 innodb_random_read_ahead 默认没有开启。
如何对预读失效进行优化?
要优化预读失效,思路是:
- 让预读失败的页,停留在缓冲池LRU里的时间尽可能短
- 让真正被读取的页,才挪到缓冲池LRU的头部
InnoDB 的具体解决方法
由上图可以看出 InnoDB 将 LRU List 分为两部分,默认前 5/8 为 New Sublist(新生代)用于存储经常被使用的热点数据页,后 3/8 为 Old Sublist(老生代),新读入的数据页默认被放到 Old Sublist 中,只有满足一定条件后,才会被移入 New Sublist。
新生代和老生代代比例在 MySQL 中通过参数 innodb_old_blocks_pct 控制,值的范围是5到95.默认值是37(即池的3/8)。
- 如果数据页真正被读取(预读成功),才会加入到新生代的头部
- 如果数据页没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池
举个例子,整个缓冲池如图
假如有一个页号为 50 的数据页页被预读加入缓冲池:
(a). 页号为50 的数据页只会从老生代头部插入,老生代尾部(也是整体尾部)的页会被淘汰掉,即 8 号数据页被淘汰。
(b). 假如页号为50 的数据页不被真正读取,即预读失败,它将比新生代的数据更早淘汰出缓冲池
(c). 假如 50 这一页立刻被读取到,例如SQL访问了页内的行row数据。它会被立刻加入到新生代的头部,同时新生代的页会被挤到老生代,此时并不会有页面被真正淘汰
改进版缓冲池LRU能够很好的解决“预读失败”的问题。但仍然无法解决缓冲池被污染但问题。
缓冲池污染
当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL 性能急剧下降,这种情况叫缓冲池污染。
解决方法
缓冲池加入了一个“老生代停留时间窗口”的机制:
(a). 假设T=老生代停留时间窗口
(b). 插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部
(c). 只有满足“被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部
假如批量数据扫描,有91、92、93、94、95、96、97、98、99等页面将要依次被访问
如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会置换出大量热数据。
加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。
只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。
老生代的停留时间由参数 innodb_old_blocks_time
控制,单位为毫秒,默认是1000
总结
- 缓冲池(buffer pool)是一种常见的降低磁盘访问的机制
- InnoDB的缓冲池以数据页(page)为单位缓存数据
- InnoDB 对普通 LRU 进行了优化,
- 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题。
- 同时采用老生代停留时间窗口机制,当数据页被访问且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题