MySQL是怎样运行的——第十七章

这一章主要讲Buffer Pool,也就是页缓冲池。

17.1

这一节主要是讲缓存的重要性。其实是不言而喻的,访问磁盘的速度和访问内存的速度有着数量级的差距。

17.2

这一节主要是详细介绍InnoDB的Buffer Pool

什么是BufferPool

就是页缓冲池。默认大小128MB,最小5MB。可以自己配置:

[server]
innodb_buffer_pool_size = 268435456

这样就是256MB

Buffer Pool内部组成

内部由控制块、碎片和缓冲页组成。
控制块用来存储元信息。缓冲页用来存储磁盘中的页面数据。
中间的缝隙就是外部碎片(当然也可能没有)。
在这里插入图片描述
这里说一下控制块里的元信息包括什么,大概有页面所属的表空间号、页面号、缓冲页在BufferPool中的地址、链表节点信息等。不难看出,控制块可以快速定位页的位置。同时包含了链表信息,可以形成各种静态链表。

注意,我们上一小讲说的空间大小不包含控制块大小。

Free链表

每当我们从磁盘上拉一个页到内存里时,都要放到Buffer Pool里。那怎么知道哪个页面是空着的呢?肯定不能遍历。
所以我们把所有空闲页面对应的控制块连接起来,形成一个链表。我们叫它Free链表。

为了更好的管理空闲链表,设计者单独定义了一个链表基节点。
在这里插入图片描述
注意,这个基节点的内存不算在我们配置里要求申请的内存中。这没什么影响,因为一个基节点也就40B左右。

哈希处理

缓冲池里有一个哈希表,其中的key是表空间号+页号(足够完全标识一个页了),value是缓冲页控制块。
这个哈希表可以帮助我们快速的判断一个页面是否在缓冲池里。如果在,也可以快速地找到它。

Flush链表

修改过的页面叫做脏页。脏页的控制块也被连接起来了,形成一个链表,叫做Flush链表。当然,它也有对应的链表基节点。
我们当然可以访问完一个页就刷盘,但那样速度太慢了。我们统一管理脏页,异步慢慢刷盘。

LRU链表

我们的Buffer Pool是有大小限制的,当页面存满了的时候,需要把一些页淘汰掉。为了决定淘汰哪些页面,我们在buffer pool里单独有一个LRU链表。注意和flush链表不是一个

想要速度快可以加个哈希表。
然而普通的LRU有两方面问题,首先介绍一下这两方面问题的成因。

成因1 预读

所谓预读就是说你在读取某些页面的时候,MySQL觉得你接下来要读取一些相关的页面,于是预先把那些页面加载进来了。
预读有两种:

  1. 线性预读:如果顺序访问某个区中超过innodb_read_ahead_threshold个页面(那是个系统变量),就会触发一次异步的线性预读。这个线性预读会把下一个区的所有页面预读到InnoDB中。而那个系统变量阈值默认是56。我们可以在服务器运行过程中通过启动选项来调整这个系统变量,方法是set global ······ = ······;
  2. 随机预读:如果某个区的连续13个页面都被加载到了缓冲池中,无论是不是顺序访问,mysql都会把这个区域剩下的页面加载到缓冲池里。这个功能默认是关闭的,可以通过更改innodb_random_read_ahead系统变量来打开。方法还是set global。

成因2 全表扫描

一次全表扫描会让大量的页面加载进缓冲池,但是加载进来基本不会再用。马上又会被淘汰,继续把后面的页面加载到缓冲池里。这样缓冲池其实没起到什么作用。

影响

一方面是,我们的缓冲池会被全表扫描频繁的清理,变成没用的数据。
另一方面是,我们预取的页面不一定会被访问,可他们被预取了一定会被放到头部。这样会导致一些真正有用的页面被挤到尾部淘汰。

解决办法

设计者将MySQL分成两截,一截冷区一截热区。数据刚来插入到冷区的头部(淘汰也先淘汰冷区)。只有加载到冷区后再被访问才会被移动到热区头部。
这就解决了预取的影响,因为他们不会影响所有数据,只会影响一部分。

但是全表扫描的影响还是没解决。因为访问连续的一些记录会导致每个页都在极短的时间内被大量访问,如果让他们移动到热区,还是会造成影响。因此设计者决定,只有在加入到冷区一段时间会,再被访问,才移动到热区。

这一段时间是被系统变量控制的,默认1000毫秒,足够全表扫描了。
在这里插入图片描述

最后说一下冷区热区的比例,默认是5比3。可以通过配置更改

[server]
innodb_old_blocks_pct = 40

这样冷区就是40了。当然,运行过程中也可以改,不过那时候就要通过set global这种修改系统变量的方式更改它了。

进一步优化

热区的数据根据定义必然会被快速访问,如果每访问一次就移动一下其实是很亏的,因为它们会频繁的替换第一位置,但都不会被挤出热区。
所以设计者规定,只有在热区后75%的页面被再次访问时,才移动到头部。这也符合28法则。

其他的链表

有一些存储压缩页的链表,感兴趣自己看吧,作者没讲。

刷新脏页

刷新脏页主要有两种。一种是从冷区入手,刷一部分脏页到磁盘,(备注,控制块的元信息里存储了一个页是不是脏页),另一种是从flush链表刷脏页到磁盘。

后台线程会定时的从冷区尾部扫描,如果发现脏页就刷盘。具体扫描多少页面由系统变量innodb_lru_scan_depth来指定。
后台线程也会定时的刷新Flush链表。

如果脏页太多,异步刷盘刷的太少,就会导致lru里剩下的脏页太多。如果此时用户加载新页面,就会发现没有位置了。因为都是脏页,也没有什么干净页可以淘汰,那这时候我们很有可能要同步的刷脏页到磁盘。虽然这样很慢,但是属于不得已而为之。

如果系统超级繁忙,则用户线程也可能被借去刷新脏页。这里会在讲redo的checkpoint的时候再进一步解释。

多个Buffer Pool实例

并发环境下,访问缓冲池要加锁。这样会造成性能瓶颈。因此可以多弄几个缓冲池实例。
我们可以在启动服务器的时候指定实例数:

[server]
innodb_buffer_pool_instance = 2

不过设计者规定,如果缓冲池的大小小于1g,则不会创建多个缓冲池。

innodb_buffer_pool_chunk_size

在5.7.5版本以后,我们可以在运行的过程中修改缓冲池大小。
如果我们修改了缓冲池大小,就会导致mysql在内存中重新申请一块新的连续内存空间,还会进行数据的拷贝。
但缓冲池一般来说是很大的,如果每次修改大小都拷贝所有数据,那开销太高了。
因此现在的缓冲池在申请内存的时候,其实申请的是多个大小固定的chunk。这样每次变更只需要增删chunk就可以了。

(当然,chunk size在运行过程中是不能改的,否则就和之前一样了,你改chunk size,那所有chunk不也是需要拷贝吗)

由于控制块不算在chunk内,所以实际申请的空间会比所有chunk的和大一些。
默认chunk size 是128MB。

配置时的注意事项

  • 缓冲池大小必须是chunk size * 缓冲池实例数的整数倍。这很好理解,如果不是整数倍的话就会导致小数个chunk。
  • 如果chunk size * 缓冲池实例数大于pool size,则chunk size会变成 pool size / 缓冲池实例数。这是保证每个pool最少也是一个chunk。

查看Buffer Pool的状态信息

SHOW ENGINE INNODB STATUS

在这里插入图片描述
具体解释看下面的书截图吧,没必要抄。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值