Buffer Poll

Buffer Pool 中默认的缓存页大小和在磁盘上默认的页大小是一样的,都是 16KB,为了更好的管理这些在 Buffer Pool 中的缓存页,设计 InnoDB 的大叔为每一个缓存页都创建了一些所谓的 控制信息 ,这些控制信息 包括该页所属的表空间编号、页号、缓存页在 Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息。我们就把每个页对应的控制信息占用的一块内存称为一个 控制块吧。控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前边,缓存页被存放到 Buffer Pool 后边,所以整个 Buffer Pool 对应的内存空间看起来就是这样的:

mysql 8版本还有bufferpool_缓存

Buffer Pool里缓存的不只有索引页和数据页(占很大一部分),还有undo页,自适应哈希索引,插入缓冲,锁信息等等。

Buffer Pool里有三个链表,LRU链表,free链表,flush链表。

free链表的节点是所有空闲的缓存页对应的控制块。

我们怎么知道该页在不在 Buffer Pool 中呢?

哈希,我们可以用 表空间号 + 页号 作为 key , 缓存页 作为 value 创建一个哈希表,在需要访问某个页的数据 时,先从哈希表中根据 表空间号 + 页号 看看有没有对应的缓存页,如果有,直接使用该缓存页就好,如果没 有,那就从 free链表 中选一个空闲的缓存页,然后把磁盘中对应的页加载到该缓存页的位置。

flush链表里的节点是所有脏页的控制块。

LRU链表:

  1. 一个简单的lru链表是
  • 如果该页不在 Buffer Pool 中,在把该页从磁盘加载到 Buffer Pool 中的缓存页时,就把该缓存页对应的 控制块 作为节点塞到链表的头部。
  • 如果该页已经缓存在 Buffer Pool 中,则直接把该页对应的 控制块 移动到 LRU链表 的头部。
  1. 但这会有一些问题:
  • innodb提供了预读功能,预读 又可以细分为下边两种:
  • 线性预读:设计 InnoDB 的大叔提供了一个系统变量 innodb_read_ahead_threshold ,如果顺序访问了某个区 ( extent )的页面超过这个系统变量的值,就会触发一次 异步 读取下一个区中全部的页面到 Buffer Pool 的请求。
  • 随机预读:如果 Buffer Pool 中已经缓存了某个区的13个连续的页面,不论这些页面是不是顺序读取的,都会触发 一次 异步 读取本区中所有其的页面到 Buffer Pool 的请求。默认是关闭的。

这样如果预读的这样页都没有被用到的话,就会导致真正的热点页被排挤掉

  • 全表扫描:扫描全表意味着什么?意味着将访问到该表所在的所有页!假设这个表中记录非常多的话,那该表会占用特 别多的 页 ,当需要访问这些页时,会把它们统统都加载到 Buffer Pool 中,这也就意味着吧唧一下, Buffer Pool 中的所有页都被换了一次血。这会大大降低命中率
  1. 于是设计 InnoDB 的大叔把这个 LRU链表 按照一定比例分成两截,分别是:
  • 一部分存储使用频率非常高的缓存页,所以这一部分链表也叫做 热数据 ,或者称 young区域 。
  • 另一部分存储使用频率不是很高的缓存页,所以这一部分链表也叫做 冷数据 ,或者称 old区域 。

mysql 8版本还有bufferpool_缓存_02

但注意:我们是按照某个比例将LRU链表分成两半的,不是某些节点固定是young区域的,某 些节点固定是old区域的,随着程序的运行,某个节点所属的区域也可能发生变化。默认情况下old区占比是3/8。

然后规定:

  • 当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓存页时,该缓存页对应 的控制块会被放到old区域的头部,这样针对预读到 Buffer Pool 却不进行后续访问的页面就会被逐渐从 old 区域逐出,而不会影响 young 区域中被使用比较频繁的缓存页。
  • 并且在对某个处在 old 区域的缓存页进行第一次访问时就在它对应的控制块中 记录下来这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该页面就不会被 从old区域移动到young区域的头部,否则将它移动到young区域的头部。

还有一个优化是:只有被访问的缓存页位于 young 区域的 1/4 的后边(后3/4),才会被移动到 LRU链表 头部,这样就 可以降低调整 LRU链表 的频率,从而提升性能

Buffer Pool是可以有多个的,添加页面时根据哈希决定添加到哪个Buffer Pool。

Checkpoint

checkpoint技术是为了解决一下几个问题:

  • 缩短数据库恢复时间
  • 缓冲池不够用时,将脏页刷新到磁盘
  • 重做日志不可用时,刷新脏页

事务提交是先写redolog,再修改页。

checkpoint分为sharp checkpoint和fuzzy checkpoint。

当数据库关闭时,会发生sharp checkpoint,将所有脏页都刷新到磁盘。其他情况使用fuzzy checkpoint。

  1. Master 线程会每隔一段时间从缓冲池的flush链表异步刷新一定比例脏页回磁盘
  2. 当LRU链表的空闲页不够时,page clearner线程会将LRU链表尾端的页移除,如果这些页里有脏页,会发生checkpoint。
  3. 重做日志不可用时,会异步从flush链表中选取一些页,强制刷回磁盘。
  4. 脏页数量太多,达到75%时也会强制刷盘

redolog刷盘策略有三种:

  • 参数为0:表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日 志的同步)
  • 参数为1:表示每次事务提交时都将进行同步,刷盘操作( 默认值 )
  • 参数为2:表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自 己决定什么时候同步到磁盘文件。

redolog是在事务提交之前就在写的,事务提交时,会根据设定的值进行刷盘。

mysql 8版本还有bufferpool_缓存_03

从redo log buffer到page cache的操作是后台线程每隔1s做的。

redolog files:

mysql 8版本还有bufferpool_缓存_04

日志文件最大有100个,默认有两个(ib_logfile 0,ib_logfile 1)。采用循环使用的方式向redo日志文件组里写数据的话,会导致后写入的redo日志覆盖掉前边写的redo日 志?当然!所以InnoDB的设计者提出了checkpoint的概念。

mysql 8版本还有bufferpool_控制块_05

如果 write pos 追上 checkpoint ,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得 停下来,清空一些记录,把 checkpoint 推进一下。