3分钟了解InnoDB的读写缓冲

缓冲池

存放表数据和索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘I/O,加速访问

缓冲池的数据管理

最常用的就是LRU算法,redis、memcache、OS都会用,但是MySQL不一样

传统的LRU算法

最常见的玩法是:有一个列表,新访问的数据放在头部,空间不足时,淘汰末尾元素。

例如:

假设我们访问的数据,在编号为3的页中:

编号3的页在队列中,移动到队列头,没有页被淘汰

接下来,假设我们要访问编号15的页

因为编号为15的页不在队列中,在队列头插入,编号9的页被淘汰

传统的LRU简单直观,为什么MySQL没有直接使用呢?

因为,MySQL有两类操作会出现问题:

  1. 预读
    预读是一种将多个页读取到缓冲池的I/O请求,预测这些页很快会被访问。实际应用中,这些页可能并不会访问
  2. 全表扫描
    mysqldump或者全表查询会导致加载大量数据,大量的热点数据被驱逐,导致性能急剧下降

该如何优化呢?

  1. 让非热点数据页停留时间尽可能短
  2. 让真正需要的页才移动到头部

MySQL的中点插入LRU

针对预读的问题,解决思路是:

  1. 将LRU列表分成两部分,新页列表和旧页列表
  2. 新页列表尾部与旧页列表头部相连
  3. 新访问的页会在旧页列表头部插入

这样,预读的页如果不被访问,就不会进入新页列表,并且很快老化而被驱逐

例如:

假设,我们通过预读加载了编号37的页:

编号为37的页插入到旧页列表头部,同时淘汰了编号23的页

接下来,假设我们读操作需要访问的数据在编号37的页中:

编号为37的页移动到新页列表头部,编号为7的页从新页列表尾部移动到旧页列表头部

对于全表扫描,这些措施还不够,因为一个数据也可能被连续访问几次,该怎么改进呢?
MySQL增加停留时间窗机制,即在时间T内,不论访问多少次,都不会移动到新页列表

上述算法,对应哪些参数呢?

  • innodb_old_blocks_pct
    旧页列表的比例,默认是37,即占3/8。取值范围是5~95,如果设置成95,算法就近似于传统的LRU
  • innodb_old_blocks_time
    旧页列表停留时间,单位ms,默认1000。数据首次访问并且停留时间超过配置,才能进入新页列表

变更缓冲

对于读请求,缓冲区能够减少I/O次数,从而提升性能,那么对于写请求呢?

对于写请求存在两种场景:

  • 情况一:修改的页恰好在缓冲区
    1. 直接修改页,一次内存操作
    2. 请求写入重做日志,一次顺序写
  • 情况二: 修改的数据还没有加载到缓冲池
    1. 读取磁盘,将索引数据加载到缓冲区,一次随机读
    2. 修改页,一次内存操作
    3. 请求写入重做日志,一次顺序写

因此,如果没有命中缓冲区,那么会出现一次随机I/O,对于写多读少的场景,是否还有优化的空间呢?
答案就是变更缓冲。

什么是变更缓冲呢?

变更缓冲区是缓冲池的一部分,存储对未加载到缓冲池的辅助索引的修改。等到数据被读取时,在将数据合并到缓冲池。如图:

因此上述的第二种情况修改成:

  1. 数据写入变更缓冲,一次内存操作
  2. 请求写入重做日志,一次顺序写

虽然首次加载数据时,会多一次内存操作,即合并变更缓冲内的变更记录,但是相比于一次磁盘I/O,开销可以忽略不计

如何保证数据一致性?

  1. 变更缓冲不仅仅是内存的一块区域,也会定期写入系统表空间
  2. 在加载索引时,会有合并机制,保证缓冲池中的索引数据是最新的

配置参数

  • innodb_change_buffering
    变更缓冲的开关,默认支持全部类型操作。none、inserts、deletes、changes、purges、all
  • innodb_change_buffer_max_size
    变更缓冲占缓冲池的百分比。默认是25,最大50
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容