对于MySQL的读请求来说,InnoDB维护了一个缓冲池 buffer pool,避免每次查询数据都进行磁盘IO,有效地减少了磁盘IO,提升了性能。
作用
缓冲池存储了最近访问或经常访问的数据页和索引页,当查询操作需要读取数据时,InnoDB首先在缓冲池里查找所需的页,如果找到了(命中),则无需访问磁盘,这样可以大大减少数据访问的延迟。
todo:缓冲区还缓存了最近修改的数据,这些数据在合适的时机会被写回到磁盘上,这个过程称为刷盘flush或写入write
工作原理
当一个数据页被访问的时候,会被加载到缓冲池里。如果该页已经在缓冲池中,则直接使用;如果不在,则在磁盘读取并放入缓冲池。
InnoDB会管理和淘汰缓冲池中的页,来保证只把最热的数据放在最近的地方,以最大限度地降低磁盘访问
管理与淘汰
预读
磁盘读取数据地时候是按页读取,一次至少读一页数据,一般是4K,如果未来要读取地数据就在页中,能够省去后续地磁盘IO,提高效率。这是因为局部性原理,数据访问一般都遵循集中读写的原则,使用一些数据,大概率会使用附近的数据。
所以,缓冲池一般也是按页缓存数据,把一些可能要访问的页提前加入缓冲池,避免未来的磁盘IO操作。
这里能想到的一点就是 缓存这边的预缓存策略。比如社区里返回一篇文章和下面的评论,把第一层评论的所有评论缓存一下。
那么InnoDB是用什么算法管理缓冲页的?
传统LRU
把入缓冲池的页放到LRU的头部,作为最近访问的元素,从而最晚被淘汰。
如果页已经在缓冲池里,就移动到LRU头部,没有页被淘汰;
如果页不在缓冲池里,放入LRU头部,淘汰LRU尾部页
但是这种算法用在MySQL这里就有2个问题:预读失效和缓冲池污染
预读失效
提前把页放入了缓冲池但是实际上并没有从页中读取数据
如何解决?
让预读失败的页,停留在缓冲池LRU里的时间尽可能短
让真正被读取的页,才能挪到缓冲池LRU的头部,这样可以保证真正被读取的热数据保留在缓冲池的时间尽可能长
具体方法
将LRU分为新生代和老生代两个部分,新老生代首尾相连,即,新生代后面是老生代
新页(例如被预读地页)加入缓冲池时,只加入到老生代头部
如果数据真正被读取(预读成功),才会被加入到新生代的头部
如果没有被读取,会币新生代的热数据页更早被淘汰出缓冲池
但是这样依旧解决不了缓冲池污染
缓冲池污染
当某一个SQL语句,需要批量扫描大量数据时,可能导致把缓冲池所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降
在扫描的时候,所有的数据页都会被加到新生代头部,但是只访问一次,真正的热数据被大量换出
如何解决?
使用老生代停留时间窗口的机制,假设T为老生代停留时间窗口,插入老生代头部的页,即使立刻被访问,也不会立刻被放入新生代头部。只有满足被访问并且在老生代停留时间大于T,才会被放入到新生代头部。
缓冲池的分配
缓冲池被划分为多个缓冲块Buffer Blocks,每个缓冲块通常为一页,页大小默认为16KB
缓冲池中的页可以是热页(频繁访问的页)或冷页(不常访问的页),InnoDB会根据访问频率动态调整这些页在缓冲池中的位置。
缓冲池的刷新
为了保证数据的持久性,InnoDB会定期将缓冲池中脏页(被修改的页)刷新回磁盘,这个过程可以通过多种方式触发,包括手动刷新、自动刷新、缓冲池满的页替换等
缓冲池的监控
SHOW ENGINE INNODB STATUS命令来查看缓冲池的当前状态
缓冲池实例
在多实例环境中,每个InnoDB实例都有自己的缓冲池,从MySQL5.7开始,支持多个缓冲池实例
缓冲池的配置和优化
缓冲池的大小和配置需要根据实际的工作负责和可用内存进行调整
innodb_buffer_pool_size:配置缓冲池的大小,在内存允许的情况下,DBA往往会建议调大这个参数,越多数据和索引放到内存里,数据库的性能会越好
innodb_old_blocks_pct:老生代占整个LRU链长度的比例,默认是37,即整个LRU中新生代与老生代长度比例是63:37。
innodb_old_blocks_time:老生代停留时间窗口,单位是毫秒,默认是1000,即同时满足"被访问"与"在老生代停留时间超过1秒"两个条件,才会被插入到新生代头部