什么是Buffer Pool

为了缓存磁盘中的页,MySQL服务器启动的时候就向操作系统申请了一片连续的内存,他们给这片内存起了个名,叫做Buffer Pool(中文名是缓冲池)。innodb_buffer_pool_size参数的值,它表示Buffer Pool的大小。

Buffer Pool内部组成

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

为了更好的管理这些在Buffer Pool中的缓存页,每一个缓存页都创建了一些所谓的控制信息,这些控制信息包括该页所属的表空间编号、页号、缓存页在Buffer Pool中的地址、链表节点信息、一些锁信息以及LSN信息,称之为 控制块 ,控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中。


free链表的管理

  • 申请和分配好Buffer Pool之后,如何使用和管理呢?从磁盘上读取一个页到Buffer Pool中的时候该放到哪个缓存页的位置呢?或者说怎么区分Buffer Pool中哪些缓存页是空闲的,哪些已经被使用了呢?

初始化的时候,我们可以把所有空闲的缓存页对应的控制块作为一个节点放到一个链表中,这个链表也可以被称作free链表(或者说空闲链表)

如下图所示:

链表的基节点占用的内存空间并不包含在为Buffer Pool申请的一大片连续内存空间之内,而是单独申请的一块内存空间。

每当需要从磁盘中加载一个页到Buffer Pool中时,就从free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上(就是该页所在的表空间、页号之类的信息),然后把该缓存页对应的free链表节点从链表中移除,表示该缓存页已经被使用了~

缓存页的哈希处理

  • 当我们需要访问某个页中的数据时,就会把该页从磁盘加载到Buffer Pool中,如果该页已经在Buffer Pool中的话直接使用就可以了。那么问题也就来了,我们怎么知道该页在不在Buffer Pool中呢?难不成需要依次遍历Buffer Pool中各个缓存页么?

我们可以根据表空间号 + 页号来定位一个页的,也就相当于表空间号 + 页号是一个key缓存页就是对应的value,来构建一个hash表。

flush链表的管理

flush链表的由来

  • 如果我们修改了Buffer Pool中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页也被称为脏页(英文名:dirty page)。当然,最简单的做法就是每发生一次修改就立即同步到磁盘上对应的页上,但是频繁的往磁盘中写数据会严重的影响程序的性能。
  • 如果不立即同步到磁盘的话,那之后再同步的时候我们怎么知道Buffer Pool中哪些页是脏页,哪些页从来没被修改过呢?总不能把所有的缓存页都同步到磁盘上吧,假如Buffer Pool被设置的很大,比方说300G,那一次性同步这么多数据岂不是要慢死!所以,我们不得不再创建一个存储脏页的链表,凡是修改过的缓存页对应的控制块都会作为一个节点加入到一个链表中,因为这个链表节点对应的缓存页都是需要被刷新到磁盘上的,所以也叫flush链表

如下图所示:

LRU链表的管理

出现LRU链表的原因

当然是Buffer Pool内存不够用了

Buffer Pool对应的内存大小毕竟是有限的,如果需要缓存的页占用的内存大小超过了Buffer Pool大小,也就是free链表中已经没有多余的空闲缓存页,会淘汰最近最少使用的缓存页。

mysql中的LRU链表

简单的LRU链表

  • 如果该页不在Buffer Pool中,在把该页从磁盘加载到Buffer Pool中的缓存页时,就把该缓存页对应的控制块作为节点塞到链表的头部。
  • 如果该页已经缓存在Buffer Pool中,则直接把该页对应的控制块移动到LRU链表的头部。

划分区域的LRU链表

简单的LRU链表用了没多长时间就发现问题了,因为存在这两种比较尴尬的情况:

  • InnoDB提供了一个看起来比较贴心的服务——预读(英文名:read ahead)。所谓预读,就是InnoDB认为执行当前的请求可能之后会读取某些页面,就预先把它们加载到Buffer Pool中。根据触发方式的不同,预读又可以细分为下边两种:

    • 线性预读:mysql提供了一个系统变量innodb_read_ahead_threshold,如果顺序访问了某个区(extent)的页面超过这个系统变量的值,就会触发一次异步读取下一个区中全部的页面到Buffer Pool的请求
    • 随机预读:如果Buffer Pool中已经缓存了某个区的13个连续的页面,不论这些页面是不是顺序读取的,都会触发一次异步读取本区中所有其的页面到Buffer Pool的请求。设计InnoDB的大叔同时提供了innodb_random_read_ahead系统变量,它的默认值为OFF,也就意味着InnoDB并不会默认开启随机预读的功能。

    预读本来是个好事儿,如果预读到Buffer Pool中的页成功的被使用到,那就可以极大的提高语句执行的效率。可是如果用不到呢?这些预读的页都会放到LRU链表的头部,但是如果此时Buffer Pool的容量不太大而且很多预读的页面都没有用到的话,这就会导致处在LRU链表尾部的一些缓存页会很快的被淘汰掉,也就是所谓的劣币驱逐良币,会大大降低缓存命中率。

  • 有大表的全表扫描语句出现

    表中记录非常多的话,那该表会占用特别多的,当需要访问这些页时,会把它们统统都加载到Buffer Pool,后果可想而知。

基于以上两种情况,mysql 把这个LRU链表按照一定比例分成两截,分别是:

  • 一部分存储使用频率非常高的缓存页,所以这一部分链表也叫做热数据,或者称young区域
  • 另一部分存储使用频率不是很高的缓存页,所以这一部分链表也叫做冷数据,或者称old区域

变量innodb_old_blocks_pct的值来确定old区域在LRU链表中所占的比例,old区域在LRU链表中所占的比例是37%。我们可以在启动时修改innodb_old_blocks_pct参数来控制old区域在LRU链表中所占的比例:

[server]
innodb_old_blocks_pct = 40

有了这个被划分成youngold区域的LRU链表之后,设计InnoDB的大叔就可以针对我们上边提到的两种可能降低缓存命中率的情况进行优化了:

  • 当磁盘上的某个页面在初次加载到Buffer Pool中的某个缓存页时,该缓存页对应的控制块会被放到old区域的头部。这样针对预读到Buffer Pool却不进行后续访问的页面就会被逐渐从old区域逐出,而不会影响young区域中被使用比较频繁的缓存页。

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

    在进行全表扫描时,虽然首次被加载到Buffer Pool的页被放到了old区域的头部,但是后续会被马上访问到,每次进行访问的时候又会把该页放到young区域的头部,这样仍然会把那些使用频率比较高的页面给顶下去。

    所以我们只需要规定,在对某个处在old区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内,那么该页面就不会被从old区域移动到young区域的头部,否则将它移动到young区域的头部。上述的这个间隔时间是由系统变量innodb_old_blocks_time控制的,默认值是1000,单位是毫秒。

更进一步优化LRU链表

我们每次访问一个缓存页就要把它移动到LRU链表的头部,这样开销是不是太大啦,毕竟在young区域的缓存页都是热点数据,也就是可能被经常访问的,这样频繁的对LRU链表进行节点移动操作开销是很大的。

为了解决这个问题其实我们还可以提出一些优化策略,比如只有被访问的缓存页位于young区域的1/4的后边,才会被移动到LRU链表头部,这样就可以降低调整LRU链表的频率,从而提升性能(也就是说如果某个缓存页对应的节点在young区域的1/4中,再次访问该缓存页时也不会将其移动到LRU链表头部)。

刷新脏页到磁盘

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

  • LRU链表的冷数据中刷新一部分页面到磁盘。

    后台线程会定时从LRU链表尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定,如果从里边儿发现脏页,会把它们刷新到磁盘。这种刷新页面的方式被称之为BUF_FLUSH_LRU

  • flush链表中刷新一部分页面到磁盘。

    后台线程也会定时从flush链表中刷新一部分页面到磁盘,刷新的速率取决于当时系统是不是很繁忙。这种刷新页面的方式被称之为BUF_FLUSH_LIST

多个Buffer Pool实例

在多线程环境下,访问Buffer Pool中的各种链表都需要加锁处理啥的,在Buffer Pool特别大而且多线程并发访问特别高的情况下,单一的Buffer Pool可能会影响请求的处理速度。所以在Buffer Pool特别大的时候,我们可以把它们拆分成若干个小的Buffer Pool,每个Buffer Pool都称为一个实例,它们都是独立的,独立的去申请内存空间,独立的管理各种链表,独立的吧啦吧啦,所以在多线程并发访问时并不会相互影响,从而提高并发处理能力。

我们可以在服务器启动的时候通过设置innodb_buffer_pool_instances的值来修改Buffer Pool实例的个数,比方说这样:

[server]
innodb_buffer_pool_instances = 2

这样就表明我们要创建2个Buffer Pool实例,示意图就是这样:

Innodb的Buffer Pool的更多相关文章

  1. InnoDB INFORMATION_SCHEMA Buffer Pool Tables

    InnoDB INFORMATION_SCHEMA Buffer Pool Tables InnoDB INFORMATION_SCHEMA缓冲池表提供有关InnoDB缓冲池中页面的缓冲池状态信息和元 ...

  2. innodb buffer pool小解

    INNODB维护了一个缓存数据和索引信息到内存的存储区叫做buffer pool,他会将最近访问的数据缓存到缓冲区.通过配置各个buffer pool的参数,我们可以显著提高MySQL的性能. INN ...

  3. MySQL · 性能优化· InnoDB buffer pool flush策略漫谈

    MySQL · 性能优化· InnoDB buffer pool flush策略漫谈 背景 我们知道InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数 ...

  4. 14.6.3.1 The InnoDB Buffer Pool

    14.6.3.1 The InnoDB Buffer Pool InnoDB 保持一个存储区域被称为buffer pool 用于cache数据和索引在内存里, 知道InnoDB buffer pool ...

  5. 14.4.3.5 Configuring InnoDB Buffer Pool Flushing 配置InnoDB Buffer Pool 刷新:

    14.4.3.5 Configuring InnoDB Buffer Pool Flushing 配置InnoDB Buffer Pool 刷新: InnoDB执行某些任务在后台, 包括flush 脏 ...

  6. 14.4.3.1 The InnoDB Buffer Pool

    14.4.3.1 The InnoDB Buffer Pool 14.4.3.2 Configuring Multiple Buffer Pool Instances 14.4.3.3 Making ...

  7. [ERROR] InnoDB: Cannot allocate memory for the buffer pool

    :: mysqld_safe Starting mysqld daemon with databases from /data/mysqldb -- :: [Note] /usr/local/mysq ...

  8. MySQL · 引擎特性 · InnoDB Buffer Pool

    前言 用户对数据库的最基本要求就是能高效的读取和存储数据,但是读写数据都涉及到与低速的设备交互,为了弥补两者之间的速度差异,所有数据库都有缓存池,用来管理相应的数据页,提高数据库的效率,当然也因为引入 ...

  9. 【转载】MySQL · 性能优化· InnoDB buffer pool flush策略漫谈

    背景 我们知道InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数个内存块加上一组控制结构体对象组成.内存块的个数取决于buffer pool inst ...

  10. 【MySQL】InnoDB 内存管理机制 --- Buffer Pool

    InnoDB Buffer Pool 是一块连续的内存,用来存储访问过的数据页面 innodb_buffer_pool_size 参数用来定义 innodb 的 buffer pool 的大小 是 M ...

随机推荐

  1. c# HttpServer 的使用

    在很多的时候,我们写的应用程序需要提供一个信息说明或者告示功能,希望借助于HttpServer来发布一个简单的网站功能,但是又不想架一个臃肿的Http服务器功能, 这时候,标准框架提供的HttpSer ...

  2. 使用C++进行冒泡排序

    #include "pch.h" #include <iostream> using namespace std;手动输入10个数,进行冒泡排序 int main() ...

  3. 将map转为Bean的工具类 BeanUtil

    Map<String,Object> pbclwhMainMap = (Map<String,Object>)param.get("pbclwhMain") ...

  4. python脚本通过adb命令安装包

    import os os.system("adb install E:\\huaxin.apk") os.system("adb install E:\\hx_recor ...

  5. Day10-数组

    数组 一.什么是数组 数组是相同数据类型的有序集合 数字描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成 其中.每一个数据称作一个数组元素,每个数组元素可以通过一个下表来访问它们 二.数组 ...

  6. Python循环任务,错误打包输出

    有时候,多个任务循环在跑,但不想中间任何一个错误,停止主线程,但又想在主线程运行结束后,采集运行过程中所有的错误信息. 这种刁钻的想法,我自问自答,记录一下操作方法. err_list = [] ma ...

  7. Zabbix 监控服务介绍

    Zabbix 监控服务介绍 目录 Zabbix 监控服务介绍 一.Zabbix 监控服务介绍 1.1.1 Zabbix 监控服务介绍 1.1.2 如何去做监控 1.13 硬件监控 1.1.4 系统监控 ...

  8. js 导出json文件

    var data={}//处理得到的json字符串 var filename = 'json.json'//json文件名 if(typeof data === 'object'){ ddd = JS ...

  9. js- throw

    // Create an object type UserExceptionfunction UserException (message){ this.message=message; this.n ...

  10. xml与DataSet互转

    //将DataSet转换为xml字符串   public static string ConvertDataSetToXMLFile(DataSet xmlDS, Encoding encoding) ...