当前位置: 首页 > 科技观察

说说MySQL的BufferPool

时间:2023-03-20 13:26:43 科技观察

本文转载自微信公众号《程序员小凡》,作者范米莉。转载本文请联系程序员小凡公众号。前言什么是缓冲池?我们在使用mysql的时候,比如一个很简单的select*fromtable;这条语句,具体的查询数据实际上是在存储引擎中实现的。大家都知道mysql的数据其实是存放在磁盘中的。是的,如果每次查询都直接从磁盘中查询,这势必会影响性能,所以必须先将数据从磁盘中取出,然后存入内存,下次查询时直接从内存中取出。但是往往一台机器上运行的mysql进程不止一个,很多进程都需要使用内存,所以mysql中会有专门的区域来处理这些数据。这个专门为mysql准备的区域叫做bufferpool。缓冲池的工作过程下面以查询语句为例1:查询时,会先去缓冲池(内存)中查看是否有对应的数据页,如果有,直接返回2:如果缓冲池中没有相应的数据页将在磁盘中搜索数据页。如果在磁盘中找到对应的数据,则直接复制一份该页的数据到缓冲池中返回给客户端。3:下次同样的query进来,直接搜索bufferpool即可找到对应的数据返回。在座的各位相信大家应该对缓冲池有了一个大概的了解。是不是有点缓存的感觉?当然,缓冲池不是缓存那么简单。内部结构还是比较复杂的,不过没关系,我们继续往下看。Bufferpool数据管理数据管理的基本单位,毕竟bufferpool是一种内存管理。当然,数据并不是按照SQL语句一条一条来管理的,而是按照数据页来管理的。InnoDB引擎默认数据页为16kb,而缓冲池启动时默认为128M,所以有8192个数据页。而且磁盘的数据管理也是以数据页为单位进行管理的,所以每次查找数据都是先请求bufferpool。如果没有bufferpool,就会在磁盘上找到对应的数据页,然后copy到bufferpool中。客户返回。自由链表正常情况下,缓冲池必须从第一个数据页开始不断填充,一个一个写入,每次直接添加到后面。如下图所示(黄色部分表示数据已经写入),但是在实际生产环境中,并不是这样的。我们不仅有查询操作,还有删除、修改等操作,已经写入缓冲池的数据不一定总是有价值的。是的,有些数据是不需要的,需要释放相应的数据页,所以缓冲池中的数据实际上在这种情况下是断断续续的。在这种情况下,如何找到一个有效的空闲数据页空间来存储数据呢?最直观的方式就是从第一页开始逐页遍历,找到空闲的数据页。方法是可行的,但是极大的影响了效率,所以mysql在处理这个问题的时候使用了自由链表的方法来管理空闲的数据页。可以看看自由链表的结构。自由链表有一个基节点,记录了自由链表的唯一符号、链表尾节点地址、链表总长度。基节点后面会有很多控制块,控制块本身很小,只存放指向空闲数据页的指针,所以缓冲池在查找空闲数据页时可以直接使用空闲链表来查找空闲数据页。只要有一个数据页是空闲的,直接将数据页的地址添加到空闲列表中。当然,仅仅使用flush链表中的free链表并不能解决所有问题。例如:当我们执行updatetabletestsetfield_a=1;时,首先修改缓冲池中对应的数据页,然后更新磁盘中对应的数据页,(当然会有数据一致性的问题涉及到这里,mysql是用redolog来解决的,这个不在我们的文章范围内)我们把bufferpool中对应的修改数据页同步修改到磁盘中,这个过程称为“脏刷”。有一些冲洗脏物的策略。你可以使用select@@innodb_flush_log_at_trx_commit;是延迟写入,所以会出问题。mysql如何在缓冲池中找到修改后的脏数据?这里我们使用刷新列表。事实上,它更像是刷新列表而不是空闲列表。两者都是指向脏数据页的指针。刷脏的时候,直接遍历flush链表清理即可。lru链表缓冲池有一定的空间限制,默认是128M,总会有空间满的时候,所以数据页有淘汰机制,淘汰机制是lru(leastrecentlyused)。lru的原理其实很简单。使用过的数据页直接移到链表头部,等缓冲池满后直接淘汰链表尾部的数据页。lru链表的优化其实简单的lru链表也存在一定的问题。例如,在工作过程中,我们可能会使用select*fromtest等语句来执行刷数据等一些需求。如果测试表很大,很可能会一下子把bufferpool占满,之前的数据页全部淘汰掉。然后,当剩下的数据在线正常运行的时候,它会回来慢慢重新创建之前select*fromtest占用的数据页。淘汰,这将大大影响线路的性能。所以针对以上问题,mysql的缓冲池在lru的基础上进行了优化。缓冲池的lru链表将数据分为热数据块和冷数据块,比例大约为5:3。每写入一个新的数据页,都会写入冷数据区。但如果是这样的话,那么热数据区永远不会有数据,所以在写入冷数据区时,会单独记录写入时间。下次访问该数据区时,如果时间间隔大于1s,则将其存入热数据区,这样大量无辜的数据就不会被淘汰。因此,当我们执行select*fromtest语句刷新脚本时,只会占用冷数据的空间,不会影响热数据。