第 17 章 调节磁盘和CPU的矛盾——InnoDB的Buffer Pool

17.1 缓存的重要性

即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。

17.2 InnoDB的Buffer Pool

17.2.1 什么是Buffer Pool

MySQL 服务器启动时申请的用来缓存磁盘中的页的连接内存,叫做 Buffer Pool。默认 128 M,通过参数innodb_buff_pool_size来调节它的大小。

17.2.2 Buffer Pool内部组成

Buffer Pool 中默认的缓存页大小和磁盘上默认页的大小一样,都是16KB。

控制信息:与缓存页一一对应,包含该页所属表空间编号、页号、缓存页在 Buffer Pool 中的地址,链表节点、锁以及 LSN 信息等。

控制块:存储控制信息的内存块,每个控制块占用的内存大小是相同的。

在这里插入图片描述

TIPS:每个控制块占用缓存页大小的5%

17.2.3 free链表的管理

MySQL 服务器启动时初始化 Buffer Pool,将所有空闲的缓存对应的控制块作为一个节点放到一个链表中,称为 free 链表。

在这里插入图片描述

17.2.4 缓存页的哈希处理

对于 Buffer Pool 中的所有页,使用表空间号 + 页号作为 key,缓存页作为 value 创建一个哈希表,在需要访问某个页的数据时,直接从哈希表中取即可。

17.2.5 flush链表的管理

如果修改了 Buffer Pool 中某个缓存页的数据,使得它和磁盘上的页不一致了,这样的页被称为脏页(dirty page)

触发同步时,把脏页同步到磁盘。

把脏页加到一个称为 flush 的链表上。

在这里插入图片描述

17.2.6 LRU链表的管理
17.2.6.1 缓存不够的窘境

Buffer Pool 淘汰策略:Least recently used(LRU),最近最少使用

17.2.6.2 简单的LRU链表
  1. 如果该页不在 Buffer Pool 中,在把该页从磁盘加载到 Buffer Pool 中时,就把该页对应的控制块加到链表头部;
  2. 如果该页已经缓存在 Buffer Pool 中,则直接把该页对应的控制块移动到链表头部;
  3. 链表尾部的就是最近最少使用的缓存,当 Buffer Pool 空闲缓存页用完触发淘汰时,直接从链表尾部开始淘汰即可。
17.2.6.3 划分区域的LRU链表

两种特殊情况:

  1. 预读:InnoDB 认为执行当前的请求可能之后会读取某些页面,从而预先把它们加载到 Buffer Pool
  2. 某些查询语句导致一次性加载大量页到 Buffer Pool

这两种情况会导致以下结果从而降低 LRU 效率:

  1. 加载到 Buffer Pool 中的页不一定会被用到
  2. 如果大量低频使用的页被同时加载到 Buffer Pool,会导致高频使用的页被淘汰

为了应对以上的情况,InnoDB 将 LRU 链表按照一定比例分成两个部分:

  1. 热数据,或者称 young 区域,用于存储使用频率非常高的缓存页;
  2. 冷数据,或者称 old 区域,用于存储使用频率不是很高的缓存页。

在这里插入图片描述

old 区域的占比:

SHOW VARIABLES LIKE 'innodb_old_blocks_pct';

在这里插入图片描述

针对预计场景的优化

当磁盘上的某个页面初次加载到 Buffer Pool 时,其对应的控制块应放到 old 区域的头部。这样针对预读到 Buffer Pool 却不进行后续访问的页面就会逐渐从 old 区域淘汰,而不会影响 young 区的页面。

针对全表扫描,短时间内访问大量使用频率非常低的页面的场景优化

当磁盘上的某个页面初次加载到 Buffer Pool 时,其对应的控制块应放到 old 区域的头部。但是全表扫描时会访问页面中所有数据,相当于多次访问了这个页面,所以此时还不能将它放到 young 区。只有在对某个处在 old 区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续的访问时间与这个时间在某个时间间隔内,那么该页面就不会被从 old 区移动到 young 区。

SHOW VARIABLES LIKE 'innodb_old_blocks_time'

在这里插入图片描述

17.2.6.4 更进一步优化 LRU 链表

对于 young 区的缓存页,如果每次被访问就要往头部移动一次,开销太太。只有被访问的缓存页处于 young 区的后1/4,才会被移动到链表头部。

17.2.7 其他的一些链表

unzip LRU 链表,用于管理解压页

zip clean 链表,用于管理没有被解压的压缩页

zip free 数组,每个元素都代表一个链表

17.2.8 刷新脏页到磁盘

后台有专门的纯种每隔一段时间负责把脏页刷新到磁盘,这样可以不影响用户纯种处理正常的请求。

  1. 从 LRU 链表冷数据中刷新一部分页面到磁盘,这种方式被称为 BUF_FUSH_LRU。
  2. 从 flush 链表中刷新一部分页面到磁盘,这种方式被称为 BUF_FLUSH_LIST。
17.2.9 多个Buffer Pool实例

在多线程环境下,访问 Buffer Pool 的各种链表都需要加锁处理,如果并发特殊高,单一的 Buffer Pool 可能会影响请求的处理速度。所以在 Buffer Pool 特别大的时候,可以把它拆分成若干个小的 Buffer Pool,各个 Buffer Pool 之间相互独立。通过innodb_buffer_pool_instances来控制 Buffer Pool 实例的个数。

在这里插入图片描述

PS:当innodb_buffer_pool_size的值小于 1G 时,设置多个实例是无效的,InnoDB 会默认把innodb_buffer_pool_instances修改成1。

17.2.10 innodb_buffer_pool_chunk_size

Buffer Pool 以 chunk 为基本单位向操作系统申请空间。一个 Buffer Pool 实例包含若干个 chunk。

在这里插入图片描述

innodb_buffer_pool_chunk_size默认 128M,只能在服务器启动时指定,运行过程中不可修改。

17.2.11 配置Buffer Pool时的注意事项
  1. innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数(这主要是想保证每一个 Buffer Pool 实例中包含的 chunk 数量相同)。
  2. 如果在服务器启动时, innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的值已经大于 innodb_buffer_pool_size 的值,那么 innodb_buffer_pool_chunk_size 的值会被服器自动设置为 innodb_buffer_pool_size/innodb_buffer_pool_instances 的值。
17.2.12 Buffer Pool中存储的其他信息

除了缓存了磁盘上的页面以外,还有锁信息、自适应哈希索引等。

17.2.13 查看 Buffer Pool的状态信息

SHOW ENGINE INNODB STATUS结果中的BUFFER POOL AND MEMORY

在这里插入图片描述

名称含义
Total memory allocatedBuffer Pool 向操作系统申请的连续内存空间大小,包括全部控制块、缓存页、以及碎片的大小。
Dictionary memory allocated为数据字典信息分配的内存空间大小,注意这个内存空间和 Buffer Pool没啥关系,不包括在 Total memory allocated 中。
Buffer pool sizeBuffer Pool 可以容纳多少缓存 页 ,注意,单位是 页 !
Free buffersBuffer Pool 还有多少空闲缓存页,也就是 free链表 中还有多少个节点
Database pagesLRU 链表中的页的数量,包含 young 和 old 两个区域的节点数量。
Old database pagesLRU 链表 old 区域的节点数量。
Modified db pages脏页数量,也就是 flush链表 中节点的数量
Pending reads正在等待从磁盘上加载到 Buffer Pool 中的页面数量
Pending writes LRU即将从 LRU 链表中刷新到磁盘中的页面数量。
Pending writes flush list即将从 flush 链表中刷新到磁盘中的页面数量。
Pending writes single pag即将以单个页面的形式刷新到磁盘中的页面数量。
Pages made youngLRU 链表中曾经从 old 区域移动到 young 区域头部的节点数量
Page made not young在将 innodb_old_blocks_time 设置的值大于0时,首次访问或者后续访问某个处在 old 区域的节点时由于不符合时间间隔的限制而不能将其移动到 young 区域头部时, Page made notyoung 的值会加1
youngs/s每秒从 old 区域被移动到 young 区域头部的节点数量。
non-youngs/s每秒由于不满足时间限制而不能从 old 区域移动到 young 区域头部的节点数量。
Pages read 、 created 、 written读取,创建,写入了多少页。后边跟着读取、创建、写入的速率。
Buffer pool hit rate表示在过去某段时间,平均访问1000次页面,有多少次该页面已经被缓存到Buffer Pool 了
young-making rate表示在过去某段时间,平均访问1000次页面,有多少次访问使页面移动到 young 区域的头部了。
not (young-making rate)表示在过去某段时间,平均访问1000次页面,有多少次访问没有使页面移动到young 区域的头部
LRU len代表 LRU链表 中节点的数量
unzip_LRU代表 unzip_LRU链表 中节点的数量
I/O sum最近50s读取磁盘页的总数。
I/O cur现在正在读取的磁盘页数量
I/O unzip sum最近50s解压的页面数量
I/O unzip cur正在解压的页面数量

17.3 总结

  1. 磁盘太慢,用内存作为缓存很有必要
  2. Buffer Pool 本质上是 InnoDB 向操作系统申请的一段连续的内存空间,可能通过innodb_buffer_pool_size来调整它的大小
  3. Buffer Pool 向操作系统申请的连续内存块由控制块和缓存页组成,它们一一对应。
  4. free 链表:Buffer Pool 中的空闲页
  5. 使用表空间号 + 页号作为 key,缓存页作为 value 建立哈希表,以快速定位某个页
  6. flush 链表:脏页,待同步
  7. LRU 链表:分为 young 区和 old 区,优化淘汰 old 区
  8. Buffer Pool 可以有多个实例
  9. Buffer Pool 实例由若干个 chunk 组成
  10. 使用 SHOW ENGINE INNODB STATUS命令查看 Buffer Pool 的状态信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值