缓冲池
存放表数据和索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘I/O,加速访问
缓冲池的数据管理
最常用的就是LRU算法,redis、memcache、OS都会用,但是MySQL不一样
传统的LRU算法
最常见的玩法是:有一个列表,新访问的数据放在头部,空间不足时,淘汰末尾元素。
例如:
假设我们访问的数据,在编号为3的页中:
编号3的页在队列中,移动到队列头,没有页被淘汰
接下来,假设我们要访问编号15的页
因为编号为15的页不在队列中,在队列头插入,编号9的页被淘汰
传统的LRU简单直观,为什么MySQL没有直接使用呢?
因为,MySQL有两类操作会出现问题:
- 预读
预读是一种将多个页读取到缓冲池的I/O请求,预测这些页很快会被访问。实际应用中,这些页可能并不会访问 - 全表扫描
mysqldump或者全表查询会导致加载大量数据,大量的热点数据被驱逐,导致性能急剧下降
该如何优化呢?
- 让非热点数据页停留时间尽可能短
- 让真正需要的页才移动到头部
MySQL的中点插入LRU
针对预读的问题,解决思路是:
- 将LRU列表分成两部分,新页列表和旧页列表
- 新页列表尾部与旧页列表头部相连
- 新访问的页会在旧页列表头部插入
这样,预读的页如果不被访问,就不会进入新页列表,并且很快老化而被驱逐
例如:
假设,我们通过预读加载了编号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次数,从而提升性能,那么对于写请求呢?
对于写请求存在两种场景:
- 情况一:修改的页恰好在缓冲区
- 直接修改页,一次内存操作
- 请求写入重做日志,一次顺序写
- 情况二: 修改的数据还没有加载到缓冲池
- 读取磁盘,将索引数据加载到缓冲区,一次随机读
- 修改页,一次内存操作
- 请求写入重做日志,一次顺序写
因此,如果没有命中缓冲区,那么会出现一次随机I/O,对于写多读少的场景,是否还有优化的空间呢?
答案就是变更缓冲。
什么是变更缓冲呢?
变更缓冲区是缓冲池的一部分,存储对未加载到缓冲池的辅助索引的修改。等到数据被读取时,在将数据合并到缓冲池。如图:
因此上述的第二种情况修改成:
- 数据写入变更缓冲,一次内存操作
- 请求写入重做日志,一次顺序写
虽然首次加载数据时,会多一次内存操作,即合并变更缓冲内的变更记录,但是相比于一次磁盘I/O,开销可以忽略不计
如何保证数据一致性?
- 变更缓冲不仅仅是内存的一块区域,也会定期写入系统表空间
- 在加载索引时,会有合并机制,保证缓冲池中的索引数据是最新的
配置参数
-
innodb_change_buffering
变更缓冲的开关,默认支持全部类型操作。none、inserts、deletes、changes、purges、all -
innodb_change_buffer_max_size
变更缓冲占缓冲池的百分比。默认是25,最大50