MYSQL---BufferPool

系列文章目录

文章目录

  • 系列文章目录
  • 前言
  • 一、简介
  • 二、组成
    • 1. innodb架构图
    • 2. free链表
    • 3. flush链表
    • 3. LRU链表
      • 3.2 划分区域的LRU链表


前言

我们知道对于innodb存储引擎来说数据最终还是存储在磁盘上,如,.ibd、.frm文件中。现如今虽然在使用ssd固态硬盘后磁盘的读写速率得到了大大的提升。但是相对cpu的计算力磁盘的速度还是不太能跟得上,无法与cpu的处理能力相匹配。所以InnoDB存储引擎在处理客户端请求时,如果需要访问某个页的数据,并不是只加载当前数据行,而是会把完整的页中全部数据都加载到内存中并且还有可能进行预读。之后就可以进行读写了,但是读写结束之后并不会将该页的内存空间立即释放掉,而是将其缓存秋来,便于将来再次访问该页时能够节省磁盘I/O开销。而为了放置这些缓冲页,innoDB启动时就会向操作系统申请一片连续的内存空间这片内存就被命名为---Buffer Pool(缓冲池)。

一、简介

默认情况下Buffer Pool的大小只有128M,我们可以执行如下命令来查看缓存池的大小。

show GLOBAL VARIABLES LIKE '%innodb_buffer_pool_size%';

所以通常来说,我们建议一个比较合理的、健康的比例,是给buffer pool设置你的机器内存的50%~60%左右 比如你有32GB的机器,那么给buffer设置个20GB的内存,剩下的留给OS和其他人来用,这样比较合理一些。 假设你的机器是128GB的内存,那么buffer pool可以设置个80GB左右,大概就是这样的一个规则。

innodb_buffer_pool_size = 268435456

innodb_buffer_pool_size的单位是字节,上面的配置指定了Buffer_Pool的大小为256m。同时buffer允许的最小值为5M,如果设定小于此值系统会自动修改为5M。

二、组成

1. innodb架构图

MYSQL---BufferPool_第1张图片
MYSQL---BufferPool_第2张图片

Buffer_Pool会被划分为若干个页面,大小与InnoDB表空间使用的大小一致默认为16KB。为了方便管理每一个缓冲页,其上添加了许多控制信息。这些控制信息包括,页所属的表空间编号、页号、缓冲页在Buffer Pool中的地址、链表节点信息等。每个缓冲页占用的内存大小是相同的,我们把每个页对应的控制信息占用的一块内存称为一个控制块。控制块与缓冲页一一对应,都存放在Buffer Pool中。其中控制块都存放在Buffer Pool中的前面,缓冲页在后面。大概如下图所示:
MYSQL---BufferPool_第3张图片当剩余空间不够分配一对控制块和缓存页后,这个剩余空间即为碎片。如果把Buffer Pool的大小设置的刚刚好也可能不会产生碎片。每个控制块占用的空间大小并不包含在innodb_buffer_pool_size中。也就是说实际操作中InnoDB在向操作系统申请的内存空间会比innodb_buffer_pool_size大一些。一般大概在5%左右。

2. free链表

InnoDB如何能知道当从磁盘上读取一个页该放到对应Buffer Pool的哪个缓冲页中呢?此时必须有一个记录用来标识那些缓冲页是空闲的哪些缓冲页是已经被占用的。这个时候缓冲页对应的控制块就派上用场了,我们可以把所有空闲的缓冲页对应的控制块作为一个节点放到一个链表中。这个链表就可以被称为free 链表,即空闲链表。在刚完成初始化的时候Buffer Pool中,所有缓冲页都是空闲的,所以此时每个缓冲页对应的控制块都会加入到free 链表中。为了方便管理free 链表,InnoDB特意特意了一个基节点,里面存放了链表的头结点地址,尾结点地址和链表中节点的总数等信息。这里需要注意的是,链表的基节点占用的内存空间并不包含在Buffer Pool内存空间中,而是单独申请的一块内存。基节点占用内存为40字节。此后,每当需要从磁盘中加载一个页到Buffer Pool中时,就会从free 链表中取一个空闲的缓冲页对应的控制块,然后把该缓冲页对应的free链表节点移除,标识这个缓冲页已经被占用了。所

3. flush链表

当我们修改了Buffer Pool中某个缓冲页的数据后,它就与磁盘不一致了,此时这样的缓冲页被称为脏页。这个时候我们就需要将其刷新到磁盘上的对应页达到持久化的目的,但是每有一个更新我们都立即去刷新的话这样频繁的写数据会严重影响程序性能。是否能有一种解决方案避免这种窘境的发生。当需要刷新的脏页积攒到一定量后再统一进行处理,但是此后我们又怎么能知道Buffer Pool中那些是脏页,哪些是没有修改过的页呢。如果一次性把所有的页都刷新到磁盘上,这样又会徒增很多的性能损耗是程序并不能承受的。以InnoDB不得不又创建了一个专门用来存储脏页的链表,凡是被修改过的缓冲页对应的控制块都会作为一个节点加入到这个链表中。因为这个链表的节点对应的缓冲页都需要被刷新到磁盘上,所以这个链表也被称为flush链表其结构跟free 链表基本一致。注:一个缓冲页对应的控制块不可能同时出现在free 与 flush链表中。如果一个缓冲页是空闲的那它肯定不是脏页

3. LRU链表

我们开篇便知道Buffer Pool的内存大小是有限的。当我们缓存的数据页大小超过了Buffer Pool值怎么办?此时free链表中已经没有可用的空闲缓冲页了。这个时候我们不得不将一些旧的缓冲页从Buffer Pool中删除,从而能腾出空间把新的页放进来。这个时候新的问题又接踵而至了,到底该删除那些缓冲页呢?总不能随机胡乱删除吧。按照常理来说肯定要删除使用频率最少的数据,留下最近很频繁使用的。管理Buffer Pool的缓冲页其实也是这个道理。不过我们怎么才能知道那些缓冲页最近被频繁使用,那些没有使用呢。这个时候我们是否可以再创建一个链表按照频率由高到低的顺序进行排列呢?

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

如上所述一个简单的LRU链表缓冲淘汰机制就算完成了,但是这么简单的设定是否会存在很多问题呢?

3.2 划分区域的LRU链表

开篇我们提到一点InnoDB在加载数据时有可能进行预读。所谓预读,就是InnoDB 认为执行当前请求时,可能会再后面读取某些页面,于是 预先把这些可能用到的页面提前加载到了Buffer Pool中。根据触发方式可以分为以下两种:

  • 线性预读:InnoDB中提供了一个系统变量innodb_read_ahead_threshold,如果顺序访问某个区的页面超过这个阈值,就会触发一次异步读取下一个区中的全部页到Buffer_Pool的请求。这个系统变量的默认值是56。
  • 随机预读:如果一个区内连续13个页都被加载到了Buffer Pool中,无论这些页面是不是顺序读取的,都会触发一次一步读取本区中所有页面到Buffer Pool中的请求。InnoDB 通过innodb_random_read_ahead系统变量控制,默认是OFF 关闭状态。

我们知道凡事都有两面性,预读如果被命中到了确实可以大大的提升语句执行效率。可是如果预读并没有被用到呢此时这些预读的页会放到LRU链表的头部。此时Buffer Pool容量不是很大,而且很多预读页面都没有被用到的话就白白占据内存空间,导致LRU链表尾部的缓冲很快被淘汰掉,因此Buffer Pool的命中率会被大幅降低。

总得分析可能降低Buffer Pool的两种情况:

  • 加载到Buffer Pool中的页不一定用得到
  • 如果有非常多的使用频率低的页被同时加载到Buffer Pool中,则可能会把那些使用频率非常高的页从Buffer Pool中挤出去

Innodb 为了解决这个问题,将LRU链表按照一定比例分为两截:

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

在InnoDB存储引擎中我们可以通过西面命令来查看old区域在LRU链表总所占的比例:

show variables like 'innodb_old_blocks_pct';

MYSQL---BufferPool_第4张图片
从执行结果我们可以看出,old区域在LRU链表中所占比例是37%,大约为3/8。当然这个比例是可以设置的,我们可以再启动服务器时通过修改innodb_old_blocks_pct=xxx启动项来控制old区域在LRU链表中所占比例。这个系统变量属于全局变量,通过如下命令设置:

set global innodb_old_blocks_pct = 50 ;

优化方案:

  • 针对预读的页面可能不进行后续访问的优化

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

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

我们知道全表扫描是个极耗性能和时间的任务,它在系统中相对来说执行频率非常低,因为我们要尽可能的避免全表扫描。而且在执行全表扫描的过程中,即使某个页面中有很多很多条记录,尽管每读取一条记录都算是访问一次页面,但是这个过程所花费的时间也是非常短的。所以我们只需要规定,在对某个处于old区域的缓冲页进行第一次访问时,就在它对应的控制块中记录下这个访问时间,如果后续的访问时间与第一次访问时间在某个时间间隔内,那么该页面就不会从old区域移动到young区域的头部,否则将它移动到young区域的头部。这个间隔时间是由系统变量innodb_old_blocks-time控制的,默认值为1000ms。

show variables like 'innodb_old_blocks_time';

MYSQL---BufferPool_第5张图片
也就意味着对于从磁盘加载到LRU链表中的old区域的某个页来说,如果第一次喝最后一次访问该页面的时间间隔小于1s,那么改页是不会加入到young区域的。很明显,再一次全表扫描过程中,多次访问一个页面(也就是读取同一个页面中的多条记录的时间不会超过1s)。

  • young区域优化

young区并不是像我们想的那么简单,并不是每次访问直接把它移动到LRU链表的头部,这样不断的移动会造成很大的性能开销。毕竟young区的缓冲页都是热点数据。这里InnoDB设计者做了一些优化策略,规定只有被访问的缓冲页位于young区域1/4的后面时,才会被移动到LRU链表头部。这样就可以降低调整LRU链表的频率,从而提升性能。其实在开篇提到的预读机制中,Buffer Pool中有某个区的13个连续页面就会触发随机预读是不对的,其实还要求这13个页面是非常热的页面。所谓的“非常热”,指的就是整个young区域的前1/4处。
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

你可能感兴趣的:(MYSQL,java,mysql,innodb)