学习mysql7-Buffer Pool

InnoDB的Buffer Pool

为何要引入Buffer Pool

我们实际在开发的过程中常常会在。业务层和数据层中间加入缓存的一些中间件去高效的处理查询的功能,那么mysql也是一样利用的一些内存做缓存,通过上面的学习可以知道数据本身是否在磁盘上的,如果我们每次都磁盘把数据查询出来返回,整个过程是非常慢的,所以我们提前把数据缓存到内存中,开辟一块专门的内存介于用户和磁盘中,这一块空间就叫做Buffer Pool。
那么Buffer Pool占用了多大的一块内存呢,如果你没设定Buffer Pool的大小,系统默认128M作为Buffer Pool的大小,如果你内存很大你也可以自由设定Buffer Pool,但是系统规定Buffer Pool最小5M。

Buffer Pool的组成

Buffer Pool的内部组成

Buffer Pool实际上主要由缓存页和控制块组成,那怎么定义缓存页的大小呢,我们之前的内容中说过数据库中一个页的大小是16kb,所以缓存页的大小也是16k,与缓存页对应的有一块内容叫控制块,控制块和缓存页一一对应,在一块连续的内存中,他们是这样存放的。
在这里插入图片描述
再分配的过程中大概率会产生碎片
我们之前提到128M的Buffer Pool实际是不包括控制块的内容的,每个控制块⼤约占⽤缓存页⼤⼩的5%,在MySQL5.7.21这个版本中,每个控制块占⽤的⼤⼩是808字节。⽽我们设置的innodb_buffer_pool_size并不包含这部分控制块占⽤的内存空间 ⼤⼩,也就是说InnoDB在为Buffer Pool向操作系统申请连续的内存空间时,这⽚连续的内存空间⼀般会⽐innodb_buffer_pool_size的值⼤5%左右。

Free链表

我们知道系统申请了一块连续的内存Buffer Pool后,随后就需要管理这块内存空间,我们可以通过控制快管理这块内存,刚开始的时候所有的缓存页应该都处于一个空闲的状态,那么我们建立一个Free链表,把空闲的缓存页对应的控制块连接起来,系统为了更好的找到链表的头节点,定义了Free链表的基节点,大概有40kb的大小,⾥边⼉包含着链表的头节点地址,尾节点地址,以及当前链表中节点的数量等信息。这⾥需要注意的是,链表的基节 点占⽤的内存空间并不包含在为Buffer Pool申请的⼀⼤⽚连续内存空间之内。
当我们需要把数据页加载到缓存中的时候,我们可以从空闲链表里找到对应的控制块,填上对应的表空间页号等内容,然后把这个缓存页从空闲列表里移出去

如何查询对应的缓存页

比如说,现在我们用到了需要查询某个表,现在表所在页已经加载到Buffer Pool中,那我们怎么找到对应关系呢。当然最笨的方法就是🏪整个Buffer Pool可以找到对应的缓存页,为啥不能维护有一个唯一确定的值,或者说相对一个相对的关系,我们可以在加载数据页到缓存中的时候,以表空间和页号做为一个key,缓存⻚作为value创建⼀个哈希表,每次需要先查询这个哈希表找到对应的缓存页,如果有,直接使⽤该缓存页就好,如 果没有,那就从free链表中选⼀个空闲的缓存页,然后把磁盘中对应的页加载到该缓存页的位置

Flush 链表

当我们修改了Buffer Pool的某个页数据,此时并不会马上同步到磁盘上,这些缓存页数据就是脏页,当然我们也可以设置每次修改Buffer Pool的数据,就及时的同步到磁盘上,性能比较差。那我们什么时候同步这些脏页到数据库呢,或者说怎么管理脏页呢,如果每次遍历整个buffer pool,然后去做脏页的同步,在buffer pool空间比较大的情况下,是一件麻烦事,所以我们维护了一个脏页的列表,也对应了系统一块独立的空间作为基节点,也就构成了一个Flush链表。

LRU实现

缓存页Buffer Pool是一块有限的空间,我们如何合理的利用这块空间呢,如何提供查询过程的缓存命中率?
那些常常需要被使用的数据是不是不应该从缓存页踢出去呢,我们把存放最近使用的数据放在LRU的链表中,每次清理LRU的尾部数据,这个链表是为了按照最近最少使⽤的原则去淘汰缓存页
每次查询的时候,判断是不是LRU链表中,如果在链表的尾部,移到前面来,如果不是在buffer pool中,加载数据页到Buffer Pool中,并把他加到链表的头部来。
也就是说:只要我们使⽤到某个缓存页,就把该缓存页调整到LRU链表的头部,这样LRU链表尾部就是最近最少使⽤的缓存页喽

遇到问题:1.预读 2.全表扫描
预读分为线性预读,也就是说如果顺序访问了某个区(extent)的页⾯超过这个系统变量的值,就会触发⼀次异步读取下⼀个区中全部的页⾯到Buffer Pool的请求。随机预读,如果访问某个区的13个页,那么会把这个读取到Buffer Pool
全表扫描的情况下,会把大量的数据页往Buffer Pool中加载,所以整个Buffer Pool也就失去了作用

如何有效的避免这两种情况呢?
mysql设计了LRU链表分为两部分,一部分的链存了热数据也叫young区域,一部分存了冷数据也叫old区域。
上面两种情况下,他都是在old区域内处理,当预读的时候会把他放到old区域的头部,处理全表扫描的时候也把他放到了old区域的头部,这里需要注意的是,如果说后续连续的短时间内访问该页,那么不把他放到young区域头部。

还能进一步的优化LRU链表?
也就是说某个热点数据已经在young的前面部分的时候,并不会把他频繁的移动到young区域头。

数据脏页处理

从LRU链表的冷数据中刷新⼀部分页⾯到磁盘。 后台线程会定时从LRU链表尾部开始扫描⼀些页⾯,扫描的页⾯数量可以通过系统变量innodb_lru_scan_depth来指定,如果从⾥边⼉发现脏页,会把它们刷新到磁盘。这种刷新页⾯的⽅式被称之
为BUF_FLUSH_LRU。
从flush链表中刷新⼀部分页⾯到磁盘。 后台线程也会定时从flush链表中刷新⼀部分页⾯到磁盘,刷新的速率取决于当时系统是不是很繁忙。这种刷新页⾯的⽅式被称之为BUF_FLUSH_LIST。

多个Buffer Pool实例

只有内存大于一个G的时候,系统才允许创建两个Buffer Pool,为了更好的管理Buffer Pool,系统是以⼀个所谓的chunk为单位向 操作系统申请空间。也就是说⼀个Buffer Pool实例其实是由若⼲个chunk组成的,⼀个chunk就代表⼀⽚连续的内存空间,⾥边⼉包含了若⼲缓存页与其对应的控制块。
在这里插入图片描述
偷懒的解释一下chunk,默认大小是128M,innodb_buffer_pool_chunk_size的 值只能在服务器启动时指定,在服务器运⾏过程中是不可以修改的
innodb_buffer_pool_size必须是innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances的倍数
为了保证每个Buffer Pool的大小都是一样的

查看Buffer Pool信息

SHOW ENGINE INNODB STATUS
Total memory allocated:代表Buffer Pool向操作系统申请的连续内存空间⼤⼩,包括全部控制块、缓存页、以及碎⽚的⼤⼩。
Dictionary memory allocated:为数据字典信息分配的内存空间⼤⼩,注意这个内存空间和Buffer Pool没啥关系,不包括在Total memory allocated中。
Buffer pool size:代表该Buffer Pool可以容纳多少缓存⻚,注意,单位是⻚!
Free buffers:代表当前Buffer Pool还有多少空闲缓存页,也就是free链表中还有多少个节点。
Database pages:代表LRU链表中的页的数量,包含young和old两个区域的节点数量。
Old database pages:代表LRU链表old区域的节点数量。
Modified db pages:代表脏页数量,也就是flush链表中节点的数量。
Pending reads:正在等待从磁盘上加载到Buffer Pool中的页⾯数量。 当准备从磁盘中加载某个页⾯时,会先为这个页⾯在Buffer Pool中分配⼀个缓存页以及它对应的控制块,然后把这个控制块添加到LRU的old区域的头部,但是这个时候真正的磁盘页并没有被加载 进来,Pending reads的值会跟着加1。
Pending writes LRU:即将从LRU链表中刷新到磁盘中的页⾯数量。
Pending writes flush list:即将从flush链表中刷新到磁盘中的页⾯数量。
Pending writes single page:即将以单个页⾯的形式刷新到磁盘中的页⾯数量。
Pages made young:代表LRU链表中曾经从old区域移动到young区域头部的节点数量。 这⾥需要注意,⼀个节点每次只有从old区域移动到young区域头部时才会将Pages made young的值加1,也就是说如果该节点本来就在young区域,由于它符合在young区域1/4后边的要求,下⼀次访 问这个页⾯时也会将它移动到young区域头部,但这个过程并不会导致Pages made young的值加1。
Page made not young:在将innodb_old_blocks_time设置的值⼤于0时,⾸次访问或者后续访问某个处在old区域的节点时由于不符合时间间隔的限制⽽不能将其移动到young区域头部时,Page made not young的值会加1。 这⾥需要注意,对于处在young区域的节点,如果由于它在young区域的1/4处⽽导致它没有被移动到young区域头部,这样的访问并不会将Page made not young的值加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区域的头部了。
需要⼤家注意的⼀点是,这⾥统计的将页⾯移动到young区域的头部次数不仅仅包含从old区域移动到young区域头部的次数,还包括从young区域移动到young区域头部的次数(访问某个young区域的 节点,只要该节点在young区域的1/4处往后,就会把它移动到young区域的头部)。
not (young-making rate):表⽰在过去某段时间,平均访问1000次页⾯,有多少次访问没有使页⾯移动到young区域的头部。 需要⼤家注意的⼀点是,这⾥统计的没有将页⾯移动到young区域的头部次数不仅仅包含因为设置了innodb_old_blocks_time系统变量⽽导致访问了old区域中的节点但没把它们移动到young区域的次 数,还包含因为该节点在young区域的前1/4处⽽没有被移动到young区域头部的次数。
LRU len:代表LRU链表中节点的数量。
unzip_LRU:代表unzip_LRU链表中节点的数量(由于我们没有具体唠叨过这个链表,现在可以忽略它的值)。
I/O sum:最近50s读取磁盘页的总数。
I/O cur:现在正在读取的磁盘页数量。
I/O unzip sum:最近50s解压的页⾯数量。
I/O unzip cur:正在解压的页⾯数量。

注意事项

1.Buffer Pool是mysql为了查询开辟的一块内存空间
2.Buffer Pool的控制块和缓存页一一对应,缓存页大小16Kb,控制块大概在缓存页的5%
3.用Free链表管理空闲空间,Flush链表管理脏页,LRU链表做热点数据缓存
4.为了快速定位某个页是否被加载到Buffer Pool,使⽤表空间号 + ⻚号作为key,缓存页作为value,建⽴哈希表。
5.Buffer Pool用chunk为单位向内存申请空间
6.用SHOW ENGINE INNODB STATUS查看状态

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值