MySQL是怎样运行的(十)

  Buffer Pool本质上是InnoDB向操作系统申请的一段连续的内存空间,可以通过innodb_buffer_pool_size来调整它的大小。Buffer Pool向操作系统申请的连续内存空间由控制块和缓冲页组成,控制块中的信息包括该页所属的表空间编号、页号、缓冲页在Buffer Pool中的地址、链表节点等信息,每个控制块和缓冲页是一一对应的。在填充了足够多的控制块和缓冲页组合后,Buffer Pool的剩余空间可能不足以填充一组控制块和缓冲页,从而导致这部分空间无法使用。这部分空间也因此称为碎片。

free 链表的管理

  启动MySQL的时候就会完成Buffer Pool的初始化,也就是向操作系统申请一块连续的内存空间,然后把它划分成若干对控制块和缓冲页。此时还没有真实的磁盘页被缓存到Buffer Pool中,之后随着程序的运行,会不断地有磁盘上的页被缓存到Buffer Pool中。

  刚刚完成初始化的Buffer Pool中,所有的缓冲页都是空闲的,我们把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中,这个链表就是free链表(空闲链表)。每当需要从磁盘加载一个页到Buffer Pool中时,就从空闲列表中取一个缓冲页,并把该缓冲页对应的控制块的信息填上(就是该页所在的表空间号、页号之类的信息),然后把该缓冲页从空闲链表中移除(其实是移除对应的控制块),表示该缓冲页已经被使用了。

缓冲页的哈希处理

  当需要访问某个页中的数据时,就把该页从磁盘加载到Buffer Pool中。如果该页已经在Buffer Pool中的话,直接使用就可以了。如何快速地判定某个页在不在Buffer Pool中呢?我们知道,表空间号+页号可以唯一定位一个页,也就是将表空间号+页号作为key,缓冲页对应的控制块作为value建立哈希表,就可以做到快速判定了。

flush 链表的管理

  如果修改了Buffer Pool中某个缓冲页的数据,它就与磁盘上的页不一致了,这样的缓冲页也称为脏页(dirty page)。脏页是需要刷新到磁盘上的,但是如果有改动就刷新会严重影响性能,因此InnoDB会创建一个存储脏页的链表,凡是被修改过的缓冲页对应的控制块都会作为一个节点加入到这个链表中,这个链表就称为flush链表。有了flush链表之后,就可以在未来的某个时间进行刷新了。

LRU 链表的管理

  Buffer Pool的大小是有限的,如果free链表中没有多余的缓冲页了,就需要把某些旧的缓冲页从Buffer Pool中移除,然后再把新的页放进来。Buffer Pool使用LRU算法来淘汰旧的缓冲页。不过常规的LRU链表在某些情况下可能工作得不太好,比如:

  1. 加载到Buffer Pool中的页不一定被用到(InnoDB的预读特性,将可能会被访问的数据提前加载到Buffer Pool
  2. 使用频率偏低的页被同时加载到Buffer Pool中(比如不带条件的查询语句),就可能把那些使用频率非常高的页从Buffer Pool中淘汰掉

因为这两种情况的存在,InnoDB中的LRU链表会按照一定比例分成两段:

  • 一部分存储使用频率非常高的缓冲页,这一部分链表也称为热数据,或者称为young
  • 一部分存储使用频率不是很高的缓冲页,这一部分链表也称为冷数据,或者称为old

这个比例由系统变量innodb_old_blocks_pct来控制,默认37%。改进之后,首次从磁盘加载到Buffer Pool中的页会放到old区的头部,这样可以解决问题1。针对问题2,这些被同时加载到Buffer Pool中的页,会在接下来极短的时间内被访问,因此InnoDB规定如果在innodb_old_block_time间隔时间(默认1s)内就访问该页,不会把它移动到young区头部。淘汰旧的缓冲页时,首先淘汰old区的页。

刷新脏页到磁盘

  后台有专门的线程负责每隔一段时间就把脏页刷新到磁盘,这样可以不影响用户线程处理正常的请求。刷新的方式主要有以下两种:

  1. LRU链表的old区刷新一部分页面到磁盘
  2. flush链表中刷新一部分页面到磁盘

有时,后台线程刷新脏页进度比较慢,导致用户线程在准备加载一个磁盘页到Buffer Pool中时没有可用的缓冲页。这时就会查看LRU链表尾部,看是否存在可以直接释放掉的未修改缓冲页。如果没有,则不得不将LRU链表尾部的一个脏页同步刷新到磁盘。当然,系统特别繁忙时,也可能出现用户线程从flush链表中刷新脏页的情况。

其它

  Buffer Pool可以有多个实例,它们独立的申请内存空间、独立的管理各种链表,在多线程并发访问时并不会相互影响,从而提高了并发处理能力,系统变量innodb_buffer_pool_instances可以用来设置Buffer Pool实例的个数。同时Buffer Pool实例是由若干个Chunk组成的,申请内存时,是以Chunk为单位向操作系统进行申请。

Reference

  摘抄自《MySQL是怎样运行的——从根儿上理解MySQL》,小孩子4919 著。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!