mysql之InnoDB的BufferPool
# InnoDB的BufferPool
# 为什么需要缓存?
因为存储引擎需要访问某一条记录时,是通过页作为基本单位读取到内存的,但是如果每次就因为一两条数据而把一页的数据从磁盘读取到内存有点耗费性能了,这样就多了缓存
的概念,在进行完读写访问之后并不着急把该页对应的内存空间释放掉,而是将其 缓存
起来,这样将来有请求再次访问该页面时,就可以省去磁盘 IO 的开销了。
# Buffer Pool
# 概念
在 MySQL 服务器启动的时候就向操作系统申请了一片连续的内存,他们给这片内存起了个名,叫做 Buffer Pool (中文名是 缓冲池 )。
默认情况下 Buffer Pool 只有 128M 大小
参数配置
[server]
innodb_buffer_pool_size = 268435456
2
# 内部组成
控制块 ,这些控制块包括该页所属的表空间编号、页号、缓存页在 Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息,控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool的前边,缓存页被存放到 Buffer Pool 后边。
# free链表的管理
为了让存储引擎能区分,哪些缓存页被用过,哪些没有被用过,没有被用过的缓存页就用来装要被缓存的页嘛。InnoDB的大叔又给设计出了free链表
,来解决这个问题。
free链表:把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中,这个链表也可以被称作 free链表。
流程:在缓存池初始化时,大量的缓存页是都没有被使用过的,他们的控制块都会被加入到链表中(个人认为是拿到控制块的物理地址),当有缓存需要填充缓存页,那么先从free链表
取一个控制块,填充控制块信息(就是该页所在的表空间、页号之类的信息),之后就是将控制块从free链表
移除。
# flush链表管理
背景:当我们修改数据时,会先修改缓存池中的缓存页,而不会立即修改对应磁盘上的页,这时候缓存池的页的数据和磁盘上的页的数据不一致,这个页就成为脏页
。(未来会同步这个页的数据)
概念:脏页的控制块组成的链表
组成
LRU(Least Recently Used)链表管理
概念:由于这个链表是为了 按照最近最少使用
的原则去淘汰缓存页的,所以这个链表可以被称为 LRU链表
组成:控制块组成的链表,前一部分是热数据(young区域),后面一部分为冷数据(old区域)
过程:只要我们使用到某个缓存页,就把该缓存页调整到 LRU链表 的头部,这样 LRU链表 尾部就是最近最少使用的缓存页喽
##刷新脏页到磁盘
从 LRU链表 的冷数据中刷新一部分页面到磁盘。
从 flush链表 中刷新一部分页面到磁盘。
# 多个Buffer Pool实例
为了能在多线程环境下,提高查询下频率,缓存池会被分为多个小的缓存池。
当innodb_buffer_pool_size的值小于1G的时候设置多个实例是无效的,InnoDB会默认把innodb_buffer_pool_instances 的值修改为1。而我们鼓励在 Buffer Pool 大小或等于1G的时候设置多个 Buffer Pool 实例
innodb_buffer_pool_chunk_size
在 MySQL 5.7.5 之前, Buffer Pool 的大小只能在服务器启动时通过配置 innodb_buffer_pool_size 启动参数来调整大小,在服务器
运行过程中是不允许调整该值的。不过设计 MySQL 的大叔在 5.7.5 以及之后的版本中支持了在服务器运行过程中调整 Buffer Pool
大小的功能,但是有一个问题,就是每次当我们要重新调整 Buffer Pool 大小时,都需要重新向操作系统申请一块连续的内存空间,
然后将旧的 Buffer Pool 中的内容复制到这一块新空间,这是极其耗时的。所以设计 MySQL 的大叔们决定不再一次性为某个 Buffer
Pool 实例向操作系统申请一大片连续的内存空间,而是以一个所谓的 chunk 为单位向操作系统申请空间。也就是说一个 Buffer
Pool 实例其实是由若干个 chunk 组成的,一个 chunk 就代表一片连续的内存空间,里边儿包含了若干缓存页与其对应的控制块,画个图表示就是这样:
innodb_buffer_pool_chunk_size的值只能在服务器启动时指定,在服务器运行过程中是不可以修改的
总结
磁盘太慢,用内存作为缓存很有必要。
Buffer Pool 本质上是 InnoDB 向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size 来调整它的大小。
Buffer Pool 向操作系统申请的连续内存由控制块和缓存页组成,每个控制块和缓存页都是一一对应的,在填充足够多的控制块和缓存页的组合后, Buffer Pool 剩余的空间可能产生不够填充一组控制块和缓存页,这部分空间不能被使用,也被称为 碎片 。
InnoDB 使用了许 链表 来管理 Buffer Pool 。
free链表 中每一个节点都代表一个空闲的缓存页,在将磁盘中的页加载到 Buffer Pool 时,会从 free链表 中寻找空闲的缓存页。
为了快速定位某个页是否被加载到 Buffer Pool ,使用 表空间号 + 页号 作为 key ,缓存页作为 value ,建立哈希表。
在 Buffer Pool 中被修改的页称为 脏页 ,脏页并不是立即刷新,而是被加入到 flush链表 中,待之后的某个时刻同步到磁盘上。
LRU链表 分为 young 和 old 两个区域,可以通过 innodb_old_blocks_pct 来调节 old 区域所占的比例。首次从磁盘上加载到 Buffer Pool 的页会被放到 old 区域的头部,在 innodb_old_blocks_time 间隔时间内访问该页不会把它移动到 young 区域头部。在 Buffer Pool 没有可用的空闲缓存页时,会首先淘汰掉 old 区域的一些页。
我们可以通过指定 innodb_buffer_pool_instances 来控制 Buffer Pool 实例的个数,每个 Buffer Pool 实例中都有各自独立的链表,互不干扰。
自 MySQL 5.7.5 版本之后,可以在服务器运行过程中调整 Buffer Pool 大小。每个 Buffer Pool 实例由若干个 chunk 组成,每个 chunk 的大小可以在服务器启动时通过启动参数调整。
可以用下边的命令查看 Buffer Pool 的状态信息:
SHOW ENGINE INNODB STATUS\G
1