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

面试官:能谈谈MySQL的缓存池吗?

时间:2023-03-21 18:10:25 科技观察

大家好,我是狂超君。今天就来说说Mysql缓存池的原理。附上大纲,不多说,干货。前言面试官:同学,能谈谈Mysql的缓存池吗?狂超君:啊,有这么难吗,我来整理一下语言。(内心OS:这TM不简单吗?我可以跟你聊半小时!)面试官:对,给你一分钟时间想一想。....一分钟后...狂超君:我准备好了,听好了,我要开始表演了。为什么会有缓存池?Mysql的innodb存储引擎基于磁盘存储,以页为单位进行管理。在数据库系统中,CPU速度和磁盘速度的差距非常大。为了尽可能弥补差距,缓冲池的概念被提出。因此,缓存池简单来说就是一块“内存区”。内存的速度是用来弥补磁盘速度慢的,从而对数据库的性能造成影响。缓存池“读操作”的基本原理:要读取数据库中的一个页面,首先将从磁盘读取的页面存储在缓存池中,下次读取相同的页面时,先判断该页面是否在in中缓存池。如果是,则说明该页在缓存池中命中,则直接读取该页,否则,则读取磁盘上的页。“写操作”:对于数据库中的页面修改操作,首先修改缓存池中的页面,然后以一定的频率刷新到磁盘中。它不会在每次页面变化时刷新回磁盘,而是通过检查点机制将页面刷新回磁盘。可以看出,无论是读操作还是写操作,都是对缓冲池进行操作,而不是直接对磁盘进行操作。BufferPool结构BufferPool是一块连续的内存空间,innodb存储引擎通过pages来管理这块内存。缓存池的结构如下图所示:可以看到缓存池包括数据页、索引页、插入缓存、自适应哈希索引、锁信息和数据字段。其中,数据页和索引页会占用大部分内存。“可是,innodb是怎么管理缓存池里那么多page的呢?”innodb为了更好的管理这些缓存页,为每个缓存页创建了一些所谓的控制信息,其中包括该页所属的页:表空间号(sapceid)页号(pagenumber)缓冲区中的页地址Pool一些锁信息和LSN信息日志序号其他控制信息每个缓存页对应的控制信息的内存大小是一样的,我们把每个页对应的控制信息占用的一块内存称为“控制块””。“控制块”和缓存页是一一对应的,它们都存放在BufferPool中,其中控制块存放在BufferPool前面,缓存页存放在BufferPool中缓冲池的背面。BufferPool对应的内存空间示意图:缓冲池参数设置innodb_buffer_pool_size:缓冲池的大小最多设置为物理内存的80%innodb_buffer_pool_instance:设置多少个缓冲池,通常建议设置bufferpool的个数与CPU的个数,多个bufferpool可以减少数据库内部的资源竞争,增加并发访问数据库的能力,即如果两者都将插入到新生代的headercachepool管理中同时满足“被访问”和“在老年代停留1秒以上”的条件。服务器运行时,需要完成BufferPool的初始化过程,即分配BufferPool的内存空间,将其划分为若干对控制块和缓存页。但是,此时没有真正的磁盘页面缓存在缓冲池中。随着程序的运行,磁盘上的页面会不断地缓存在BufferPool中。在使用过程中,为了记录有哪些缓存页可用,我们将所有空闲页打包成一个节点,形成一个链表,可以称为Freelinkedlist(自由链表)。因为新初始化的BufferPool中的所有缓存页都是空闲的,所以每个缓存页都会被添加到Free列表中。为了方便Free链表的管理,专门为这个链表定义了一些“控制信息”,包含链表的头节点地址、尾节点地址、当前链表中的节点数。另外,Free链表的每个节点都记录了一个“缓存页控制块”的地址,每个“缓存页控制块”记录了对应的“缓存页地址”,所以相当于每个Free链表列表节点对应一个空闲缓存页。给大家画个结构图:这张图怎么样,你懂了吧!2.Lru链表Lru链表用于管理已读取的页面。数据库刚启动的时候,Lru链表是空的。时间页面也放在空闲列表中。当需要读取数据时,会从Free链表中请求一个page,将从磁盘读取的数据放到请求的page中。这个页面的集合称为Lru链表。3、Flush链表Flush链表用于管理修改过的页面。BufferPool中被修改的页面也称为“脏页”。Lru链表和Flush链表都存在脏页。是指向Lru链表中特定数据的指针。因此,只有当Lru链表中的页面第一次被修改时,相应的指针才会存储在Flush中。如果后面修改了页面,则直接更新Lru链表中该页面对应的数据。三者的关系如下:读操作BufferPool最重要的功能之一就是“加速读”。加速读是指当需要访问一个数据页时,如果该页已经在缓冲池中,那么就不需要访问磁盘,直接从缓冲池中获取该页的内容即可。当我们需要访问某个页面中的数据时,该页面就会被加载到BufferPool中。如果页面已经在BufferPool中,可以直接使用。问题:那么如何快速找到BufferPool中的页面呢?为了避免在查询数据页时扫描LRU,实际上是根据表空间号+页号来定位一个页,相当于表空间号+页号是一个key,缓存页就是对应的值。以表空间号+页码为键,缓存页为值创建哈希表。当需要访问某个page的数据时,首先根据哈希表Page中的表空间号+页号检查是否有对应的缓存。如果是这样,直接使用缓存页面即可。如果没有,则从Free链表中选择一个空闲的缓存页,然后从磁盘中加载相应的页面到缓存页所在的位置。每当一个页面需要从磁盘加载到BufferPool中时,从Free链表中取出一个空闲的缓存页面,并填入该缓存页面对应的控制块的信息,然后对应的Free链表缓存页的节点从链表中移除,说明缓存页已经被使用,将该页写入Lru链表。在初始化的时候,Bufferpool中的所有页面都是空闲页面。当需要读取数据时,会从Free链表中请求页面。但是物理内存不能无限增加,但是数据库中的数据是不断增加的。,所以Free链表的页将被用完。因此,需要考虑从Bufferpool中删除部分缓存页面,然后需要考虑如何删除,删除哪些缓存页面。假设一共有n个页面被访问,被访问页面在缓存中的次数除以n就是缓存命中率。缓存命中率越高,与磁盘的IO交互越少。为了提高缓存命中率,InnoDB对传统的LRU算法进行了优化,解决了两个问题:1.预读失效2.Bufferpool污染和写操作Bufferpool的另一个主要功能是“加速写入”,即,当需要修改一个页面时,先在缓冲池中修改该页面,并记录相关的redolog,完成对这个页面的修改。修改后的页面实际上是刷新到磁盘,这是后台刷新线程完成的。之前的页面更新先在缓存池中进行,然后与磁盘上的页面不一致,这样的缓存页面称为脏页(dirtypage)。问题:这些修改过的页面什么时候刷新到磁盘?它们以什么顺序刷新到磁盘?最简单的方法是每次发生修改时立即同步到磁盘上的相应页面,但是频繁地向磁盘写入数据会严重影响程序的性能。因此,缓存页每次修改后,并不能立即将修改同步到磁盘,而是在以后的某个时间点同步,后台刷新线程依次刷新到磁盘,实现对磁盘。但是如果不立即同步到磁盘,后面同步时如何判断BufferPool中哪些页面是脏页,哪些页面从未被修改过呢?InnoDB不会一次性将所有缓存的页面同步到磁盘,InnoDB会创建一个链表来存储脏页。所有在Lru链表中被修改过的页面都需要添加到这个链表中,因为这个链表中的页面需要刷新到磁盘,所以这个链表也叫Flushlinkedlist,链表的结构list与Free链表一致。这里的脏页修改是指页面加载到BufferPool后第一次被修改。只有第一次修改时,才需要加入到Flush链表中。对于已经存在于Flush链表中的页面,如果该页面再次被修改,将不再放入Flush链表中。需要注意的是,脏页数据实际上还在Lru链表中,Flush链表中的脏页记录只是通过指针指向Lru链表中的脏页。并将Flush链表中的脏页按照oldest_lsn(该值表示页面第一次变化时的lsn号,对应oldest_modification,每页的头记录)排序并刷入磁盘,以及值越小,则先刷新,避免数据不一致。