MySQL面试过程中,InnoDB存储引擎的内存结构部分也是一个重要的问题点。今天就着重在这部分拆解一下。

InnoDB存储引擎架构
InnoDB存储引擎架构分为三层
内存结构(In-Memory Structure) 存在于MySQL服务进程中
OS Cash 属于内核态内存
磁盘结构 (On-Disk Structure) 存在于文件系统上
本文主要拆解一下内存结构部分,这个是面试环节中比较常问到的问题。
内存结构
Innodb 内存结构的四大核心组件
Buffer Pool
Change Buffer
Log Buffer
自适应哈希索引(Adaptive Hash Index)
四大组件的作用
Buffer Pool
由于内存的价格高,容量有限,无法比拟磁盘容量,因此需要把最热的数据放到最近的地方,以最大限度的降低磁盘访问
Buffer Pool 以页为单位缓存数据,每个页的大小通常为4K,MySQL为16K
缓冲池的常用管理算法为LRU,OS 和 memcache都是用这种算法
传统的LRU算法
将最新加入到缓冲池中的页放到LRU的头部,从而最晚被淘汰, 分为两种情况:
1. 需要用到的页已经在缓冲池中了,只需要将此页移动到LRU的头部,没有页被淘汰。

缓冲池中缓存了以上5个页
现在需要访问4号页,由于4号页已经在缓冲池中了,因此只需要将4号页移动到LRU的头部,尾部的页不会被淘汰。

2. 页不在缓冲池中,需要将页加入到LRU的头部,从而尾部的页就被淘汰了。
假如需要访问的页号为50

(1):页号50 不在缓冲池当中
(2):页号50加入到LRU头部,尾部的20号页被淘汰
OS 和 Memcache 都在使用传统的LRU算法
MySQL的LRU算法
MySQL为了解决预读失效和缓冲池污染的问题对普通的LRU算法进行了优化
Q
什么是预读?
A
磁盘读取,并不是按需读取,而是按页读取。一次至少读取一页,如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。
预读失效:
预读提前把页数据加载到了缓冲池中,但最终MySQL没有从这个页中读取数据,称为预读失效。
优化预读失效的思路:
1)让预读失效的页留在缓冲池中的时间尽可能短
2)真正被读取的页才移动到LRU的头部
实现方法:
1)将LRU分为两部分
新区(new block)
旧区(old block)
2)新区旧区首尾相连,新区的尾部连接旧区的头部。
3)新页加入到缓冲池时,只会加入到旧区的头部
如果新页预读成功,就是移动到新区的头如果新页预读失败,没有被真正使用,只会比新区的热数据更早的淘汰掉

缓冲区污染:
当一个质量很差的sql做了全表扫描
例如:select * from user where name like %abc%;
此类sql无法命中索引,要进行全表扫描,需要访问大量的页
1) 把页加载到缓冲池中(插入到旧区头部)
2) 从页中读出相应的row(插入到新区头部)
3) row里的name字段与字符串 abc进行比较,符合条件的放入到结果集中
4) 直到扫描完所有页的所有行
如此一来,所有页都被加入到了新区的头部,但只会访问一次,大量真实的热数据被换出了缓冲池。
如何解决缓冲池污染的问题?
MySQL缓冲池增加了一个旧区停留时间的机制
1)假设T=旧区停留时间窗口
2)插入到旧区的头部,并且立刻被访问到,不会立即插入到新区的头部
3)只有满足 "被访问" 且 "在旧区停留时间大于T" 才被插入到新区的头部
MySQL缓冲池相关的重要参数:
innodb_buffer_pool_size:
缓冲池大小,在内存允许的情况下尽量调大。
innodb_old_blocks_pct:
旧区在整个LRU链中占的比例,默认值为37。
即 新区:旧区=63:37
innodb_old_blocks_time:
旧区的停留时间窗口设置,默认值为1000毫秒
总结
1)缓冲池 Buffer Pool 是一种常见的降低磁盘访问的机制
2) 缓冲池以页为单位缓存数据
3)缓冲池常见的管理算法是LRU,OS 、Memcache也使用同样的算法
4)MySQL加入了 new block 和 old block 解决预读失效的问题,加入old block 停留时间窗口解决缓冲池污染问题




