MySQL中的Buffer pool

一、Buffer Pool是个什么东西?

数据库中的Buffer Pool是个什么东西?其实他是一个非常关键的组件,数据库中的数据实际上最终都是要存放在磁盘文件上的,如下图所示。

但是我们在对数据库执行增删改操作的时候,不可能直接更新磁盘上的数据的,因为如果你对磁盘进行随机读写操作,那速度是相当的慢,随便一个大磁盘文件的随机读写操作,可能都要几百毫秒。如果要是那么搞的话,可能你的数据库每秒也就只能处理几百个请求了! 在对数据库执行增删改操作的时候,实际上主要都是针对内存里的Buffer Pool中的数据进行的,也就是实际上主要是对数据库的内存里的数据结构进行了增删改,如下图所示。

其实每个人都担心一个事,就是你在数据库的内存里执行了一堆增删改的操作,内存数据是更新了,但是这个时候如果数据库突然崩溃了,那么内存里更新好的数据不是都没了吗? MySQL就怕这个问题,所以引入了一个redo log机制,你在对内存里的数据进行增删改的时候,他同时会把增删改对应的日志写入redo log中,如下图。

万一你的数据库突然崩溃了,没关系,只要从redo log日志文件里读取出来你之前做过哪些增删改操作,瞬间就可以重新把这些增删改操作在你的内存里执行一遍,这就可以恢复出来你之前做过哪些增删改操作了。 当然对于数据更新的过程,他是有一套严密的步骤的,还涉及到undo log、binlog、提交事务、buffer pool脏数据刷回磁盘,等等。

一句话:Buffer Pool就是数据库的一个内存组件,里面缓存了磁盘上的真实数据,然后我们的系统对数据库执行的增删改操作,其实主要就是对这个内存数据结构中的缓存数据执行的。

二、Buffer Pool这个内存数据结构是怎样的

如何配置你的Buffer Pool的大小

我们应该如何配置你的Buffer Pool到底有多大呢? 因为Buffer Pool本质其实就是数据库的一个内存组件,你可以理解为他就是一片内存数据结构,所以这个内存数据结构肯定是有一定的大小的,不可能是无限大的。 这个Buffer Pool默认情况下是128MB,还是有一点偏小了,我们实际生产环境下完全可以对Buffer Pool进行调整。 比如我们的数据库如果是16核32G的机器,那么你就可以给Buffer Pool分配个2GB的内存:innodb_buffer_pool_size = 2147483648 。 我们先来看一下下面的图,里面就画了数据库中的Buffer Pool内存组件。

数据页:MySQL中抽象出来的数据单位

假设现在我们的数据库中一定有一片内存区域是Buffer Pool了,那么我们的数据是如何放在Buffer Pool中的?

我们都知道数据库的核心数据模型就是 表+字段+行 的概念,所以大家觉得我们的数据是一行一行的放在Buffer Pool里面的吗? 这就明显不是了,实际上MySQL对数据抽象出来了一个数据页的概念,他是把很多行数据放在了一个数据页里,也就是说我们的磁盘文件中就是会有很多的数据页,每一页数据里放了很多行数据,如下图所示。

所以实际上假设我们要更新一行数据,此时数据库会找到这行数据所在的数据页,然后从磁盘文件里把这行数据所在的数据页直接给加载到Buffer Pool里去。 也就是说,Buffer Pool中存放的是一个一个的数据页,如下图。

磁盘上的数据页和Buffer Pool中的缓存页是如何对应起来的?

实际上默认情况下,磁盘中存放的数据页的大小是16KB,也就是说,一页数据包含了16KB的内容。 而Buffer Pool中存放的一个一个的数据页,我们通常叫做缓存页,因为毕竟Buffer Pool是一个缓冲池,里面的数据都是从磁盘缓存到内存去的。 而Buffer Pool中默认情况下,一个缓存页的大小和磁盘上的一个数据页的大小是一一对应起来的,都是16KB。 我们看下图,我给图中的Buffer Pool标注出来了他的内存大小,假设他是128MB吧,然后数据页的大小是16KB。

缓存页对应的描述信息是什么?

Buffer Pool中维护的数据结构是缓存页,而且每个缓存页都有它对应的描述信息。

MySQL刚启动,还没有从磁盘中读取任何数据页到内存(Buffer Pool)中,那此时Buffer Pool中所有的缓存页其实都是空的。

除了缓存页之外,Buffer Pool中存在三个双向链表。分别是FreeList、LRUList以及FlushList。这三个双向链表中维护着缓存页的描述信息。

假设读取出来了1个数据页

当你通过select读取出一个数据页之后,是需要将这个数据页加载进Buffer Pool中的缓存页中的。

那问题来了,MySQL怎么知道该将你读取出来的数据页存放在那个缓存页中呢?相信你看了上图应该也能想到答案了。FreeList这个双向链表不是存放了空闲的缓存页的描述信息吗?那从FreeList中取出一个空间缓存页的描述信息不就好了?于是得到了下面这张图:

InnoDB会将你读取出来的数据页加载进Buffer Pool中的缓存页中,然后缓存页的描述信息也会被维护进LRU链表中。链表做了冷热数据分离优化,5/8的区域是热数据区域,3/8的区域算是冷数据区域。(本质上它们都是双向链表),而你新读取的数据页会被放在冷数据区的靠前的位置上。

如果你将该数据页读取出来加载进缓存页中后,间隔没到1s,就使用该缓存页。那么InnoDB是不会将这个描述信息移动到5/8的热数据区域的。

但是当超过1s后,你又去读这个数据页。那这个数据页的描述信息就会被放到热数据区域。如下图:

假设一次性读取出来了好多数据页

MySQL是存在预读机制的,假设触发了MySQL的预读机制。一次性从磁盘中读取来N多个缓存页。会得到下面这张图:

因为发生了预读,所以你的一次磁盘IO读出了大量的数据页,但是这些数据页中很可能是有一些是你根本不需要的,仅仅是预读把它们级联查出来了。这时按老规矩,从FreeList中找到空闲的缓存页信息,然后将其从FreeList中移除。根据找到的空闲缓存页的描述信息,将从磁盘中读取出来的数据页加载进去。相应的该缓存页的描述信息也会被维护进LRU链表的冷数据区域。

这时你就会发现这种冷热数据分离的机制多么妙!即使发生了预读又怎么样?根本没有机会将热数据区的描述信息1挤下去。当内存不够用了需要将部分缓存页刷新到磁盘中时,那就从冷数据区域开始刷新好了,反正他们本来就不经常被使用。

同样的,当你超过1s后又访问了冷数据区的缓存页,比如访问了缓存页66和数据页67,该缓存页对应的描述信息是会被提升到热数据区,于是有了下面这张图:

那如果你访问上图中的数据页67,它会移动到描述信息66所在节点的前面去吗?

其实MySQL的LRU链表做了优化,数据67是不会往前跑的。

假设修改了某数据页

假设你执行了update xxx set xxx where id in (xxx,xxx,xxx,xxx);

而符合条件的数据行恰巧就在描述信息1、描述信息66、描述信息67所指向的缓存页中,那BufferPool中会发生什么呢?

如下图:

你会看到,被你修改了的缓存页的描述信息,被添加到了FlushList这个双向链表中。

想必看到这里你已经知道了,原来FlushList中的节点存放就是被修改了脏数据页的描述信息块。

随着MySQL被使用的时间越来越长,BufferPool的大小就越来越小。等它不够用的时候,就会将部分LRU中的数据页描述信息移除出去,这时如果发现被移除出来的数据页在FLushList中,就会触发fsync的操作,触发随机写磁盘。如果该数据页是干净的,那移除出去就好了。其他也不用干啥。

举个例子:假设需要将描述信息66、描述信息67指向的缓存页落盘。会得到下面这张脑图:

Buffer Pool中的描述数据大概相当于缓存页大小的5%左右,也就是每个描述数据大概是800个字节左右的大小,然后假设你设置的buffer pool大小是128MB,实际上Buffer Pool真正的最终大小会超出一些,可能有个130多MB的样子,因为他里面还要存放每个缓存页的描述数据。

三、Buffer Pool的设置和相关的优化

配置Buffer Pool的大小

buffer pool越大,MySQL的性能就越强悍。你可以像下面这样配置Buffer Pool的大小(单位是B)。

SET GLOBAL innodb_buffer_pool_size=402653184;

配置多个Buffer Pool的实例

你可以为MySQL实例配置多个Buffer Pool,每个Buffer Pool各自负责管理一部分缓存页,并且有自己独立的LRU、Free、Flush链表。

当有多线程并发请求过来时,线程可以在不同的Buffer Pool中执行自己的操作,MySQL性能就会得到很大的提升。

[server]
innodb_buffer_pool_size = xxx
innodb_buffer_pool_instances = 4

意思是将总容量为xxx的buffer pool划分成4个实例。每个实例都有 xxx/4 的容量。

参数innodb_buffer_pool_instances的最大值为64,并且想让该参数生效,innodb_buffer_pool_size容量至少是1G。

四、揭秘BufferPool的真实结构

现实中Buffer Pool动辄就占用好几G的内存,相对于直接申请几G的内存完成扩容,MySQL有更优雅的实现方式。

为了实现动态调整Buffer Pool的大小。MySQL设计了chunk 机制。

就是将每一个 Buffer Pool Instance 更加细力度化。将Buffer Pool拆分成更小的独立单元。

每个Buffer Pool划分成多个chunnk,每个chunk中维护一部分缓存页、缓存页的描述信息。同属于一个Buffer Pool的chunk共享该Buffer Pool的lru、free、flush链表。

块大小由参数innodb_buffer_pool_chunk_size控制,默认值为 128M

五、Buffer Pool相关的参数

show engine innodb status

image-20201103070730462

六、如何规划Buffer Pool大小

生产环境中应该给buffer pool设置多少内存?

我们将数据库部署在一台机器上,这台机器可能有个8G、16G、32G、64G、128G的内存大小,那么此时buffer pool应该设置多大呢? 有的人可能会想,假设我有32G内存,那么给buffer pool设置个30GB得了,这样的话,MySQL大量的crud操作都是基于内存来执行的,性能那是绝对高! 这么想就大错特错了,虽然你的机器有32GB的内存,但是你的操作系统内核就要用掉起码几个GB的内存!你的机器上可能还有别的东西在运行!你的数据库里除了buffer pool是不是还有别的内存数据结构! 所以上面那种想法是绝对不可取的! 如果你胡乱设置一个特别大的内存给buffer,会导致你的mysql启动失败的,他启动的时候就发现操作系统的内存根本不够用! 所以通常来说,我们建议一个比较合理的、健康的比例,是给buffer pool设置你的机器内存的50%~60%左右。比如你有32GB的机器,那么给buffer设置个20GB的内存,剩下的留给OS和其他人来用,这样比较合理一些。 假设你的机器是128GB的内存,那么buffer pool可以设置个80GB左右,大概就是这样的一个规则。

BufferPool总大小 = (chunkSize * bufferPoolInstanceNum)*2

确定了buffer pool的总大小之后,就得考虑一下设置多少个buffer pool,以及chunk的大小。 此时有一个很关键的公式:buffer pool总大小 = (chunk大小 * buffer pool数量) 的倍数 比如默认的chunk大小是128MB,那么此时如果你的机器的内存是32GB,你打算给buffer pool总大小在20GB左右,此时你的buffer pool的数量应该是多少个呢?

假设你的buffer pool的数量是16个,这是没问题的,那么此时chunk大小 * buffer pool的数量 = 16 * 128MB = 2048MB,然后buffer pool总大小如果是20GB,此时buffer pool总大小就是2048MB的10倍,这就符合规则了。 当然,此时你可以设置多一些buffer pool数量,比如设置32个buffer pool,那么此时buffer pool总大小(20GB)就是(chunk大小128MB * 32个buffer pool)的5倍,也是可以的。 那么此时你的buffer pool大小就是20GB,然后buffer pool数量是32个,每个buffer pool的大小是640MB,然后每个buffer pool包含5个128MB的chunk,算下来就是这么一个结果了。

七、总结

数据库在生产环境运行时,必须根据机器的内存设置合理的buffer pool的大小,然后设置buffer pool的数量,这样可以尽可能的保证你的数据库的高性能和高并发能力。 在线上运行时,buffer pool是有多个的,每个buffer pool里多个chunk但是共用一套链表数据结构,然后执 行crud的时候,就会不停的加载磁盘上的数据页到缓存页里来,然后会查询和更新缓存页里的数据,同时维护一系列的链表结构。 然后后台线程定时根据lru链表和flush链表,去把一批缓存页刷入磁盘释放掉这些缓存页,同时更新free链表。 如果执行crud的时候发现缓存页都满了,没法加载自己需要的数据页进缓存,此时就会把lru链表冷数据区域的缓存页刷入磁盘,然后加载自己需要的数据页进来。

八、参考

https://www.cnblogs.com/ZhuChangwu/p/14018273.html

https://www.cnblogs.com/wxlevel/p/12995324.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

codedot

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值