「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
MySQL相必大家都知道,那么大家是否了解MySQL中一个重要的组成部分缓冲池,也就我们常说的Buffer Pool?本篇文章我们一起来看深入了解一下究竟什么是Buffer Pool。
为什么数据库软件需要缓冲池?
大家都知道数据库中存储的数据最终都是存在磁盘上的,尽管目前硬件性能已经比早些年提升了许多,但是磁盘的读写速度,依旧感人。如果我们向数据库软件发送一条新增数据的命令,数据库软件直接去写磁盘,那响应结果就会变的很慢。如果说是一个特别复杂的查询,直接去拿磁盘上的文件,也会很慢,尽管说我们有索引,索引也是文件啊,也得去磁盘读取。
这时问题就来了,我们如何能够优化读写操作的延迟呢?对,内存,既然磁盘慢我们就用内存好了。
那么我们该如何设计让读写操作在内存中进行呢?没错,那就是缓冲池Buffer Pool。
MySQL中 缓冲池的设计
缓冲池Buffer Pool就是我们在读写数据时的一个缓冲区域,我们在响应写请求时,当它在内存中写入就可以完成响应,读取数据时,也可以将经常查询的数据,索引放在缓冲池中,这样能极大的加快查找效率。
小知识: 为什么Buffer Pool能加快速度,不把所有数据放入Buffer Pool?
- 缓存访问快,但容量小,数据库存储了200G数据,缓存容量可能只有64G;
- 缓存不光容量小而且缓存成本高,就像我们硬盘和内存条一样,一个几G的内存条价格快赶上1T的硬盘了;
- 缓存数据易丢失,无法做数据恢复,所以主要存储数据的位置还得在硬盘上,基于缓存的特点我们只能将热点数据放入缓存。
如下图,就是MySQL数据库InnoDB引擎的执行原理。
当我们在执行一条SQL时,如果是读操作,需要查找的数据所在的数据页在内存中时,则将结果返回。否则会把对应的数据页加载到内存中,然后再返回结果。如果是修改操作,需要修改的行所在的数据页在内存中,则修改后返回对应的结果。如果不在的话,则会从磁盘里将该行所对应的数据页读到内存中再进行修改。在内存中查询,修改数据可以很大程度上减少磁盘IO的次数。
小知识: MySQL通过Buffer Pool 修改数据,如果修改完成,服务宕机,没有来得及将修改的数据刷入磁盘,数据会不会丢失?
不会,这就不得不提MySQL的redo log ,从上面的途中我们也可以看出,我们提交事务的都会将提交标记写入redo log中,当机器重启会进行检测,将为刷入磁盘的数据根据日志信息写入磁盘。
小知识: 什么是Mysql 的预读特性?
数据访问,通常都遵循“集中读写”的原则,使用一些数据,大概率会使用附近的数据,这就是所谓的“局部性原理”,它表明提前加载是有效的,预读请求是一个i/o请求,它会异步地在缓冲池中预先回迁多个页面,预计很快就会需要这些页,这些请求在一个范围内引入所有页,这也是InnoDB在I/O上的一个优化。因为磁盘读写的时候,是按照页的方式来读取的(一页数据默认为 16K),每次至少读入一页的数据,如果下次读取的数据就在页中,就不用再去磁盘上读取了,从而减少了磁盘 I/O。预读有两种算法,一种是线性预读(linear read-ahead),另一种是随机预读(randomread-ahead),随机预读会带来不必要的开销,高版本MySQL(5.5+)默认是关掉的,可通过配置设置打开。
缓冲池的内部结构
我们再来看一下Buffer Pool内部是如何存储的,在缓冲池中,除数据页和索引页外还有多种类型,如下图所示。
MySQL中的Buffer Pool 是可以设置多个的,每个Buffer Pool 的默认大小是128M,而Buffer Pool 又是有一个一个chunk组成的。
在生产集群中,Buffer Pool的大小是可以根据机器的配置,自行设置的。
小知识: 生产环境的Buffer Pool的大小如何设置?
-
确定Buffer Pool总的内存
一般来说,比较合理的、健康的比例,就是设置机器内存的50%~60%左右,假设内存为32GB的机器,那么给buffer pool设置个20GB的内存,剩下的留给操作系统和其他软件来用,这样比较合理
-
接下来就是确定buffer pool的数量和单个chunk的大小,参考以下公式,以及默认大小比例即可
buffer pool总大小 = (chunk大小 * buffer pool数量) *N倍
如何管理缓冲池?
我们可以通过free、flush、lru三个链表动态配合,从而实现对缓冲池的管理。
具体流程如下:
- 执行sql需要数据时,先去buffer pool的哈希表查看是否有该缓存页(表空间+数据页号),如没有此时需要从磁盘中随机IO拿到对应的数据页,然后查看free中是否有空的缓存页节点;
- 如果free有空的缓存页则直接拿;
- 如果free中没有空的缓存页节点,此时则会触发lru链表的缓存页淘汰机制;
- 拿到空的缓存页空间后将数据页数据存至缓存页中,然后对缓存页数据进行各种操作;
- 如果对于缓存页进行了更新操作,此时会将节点加载到flush链表中;
- 整个流程中,都可能出现对lru链的节点淘汰(定时任务),每次lru的节点淘汰后会将对应得flush、free和lru链节点进行更新。
所以说我们通过free、flush、lru三个链表相互配合,没有缓存页的找free,free没有了找lru,lru淘汰后更新free和flush,定时lru淘汰,定时刷盘更新flush、lru、free。