每个缓存页对应的控制信息占用的内存大小是相同的,我们把每个页对应的控制信息占用的一块内存称为一个 「控制块」 。
「控制块」和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前边,缓存页被存放到 Buffer Pool 的后边。
Buffer Pool 对应的内存空间示意图:
缓存池参数设置
innodb_buffer_pool_size:缓存池的大小最多应设置为物理内存的 80%
innodb_buffer_pool_instance:设置有多少个缓存池,通常建议把缓存池个数设置为 CPU 的个数,多个缓存池可以减少数据库内部的资源竞争,增加数据库并发访问的能力
Buffer Pool 一个最主要的功能是「加速读」。加速读是当需要访问一个数据页面的时候,如果这个页面已经在缓存池中,那么就不再需要访问磁盘,直接从缓冲池中就能获取这个页面的内容。当我们需要访问某个页中的数据时,就会把该页加载到 Buffer Pool 中,如果该页已经在 Buffer Pool 中的话直接使用就可以了。 问题:那么如何快速查找在 Buffer Pool 中的页呢?
为了避免查询数据页时扫描 Lru,其实是根据表空间号 + 页号来定位一个页的,也就相当于表空间号 + 页号是一个 key,缓存页就是对应的 value。用表空间号 + 页号作为 key,缓存页作为 value 创建一个哈希表,在需要访问某个页的数据时,先从哈希表中根据表空间号 + 页号看看有没有对应的缓存页。
如果有,直接使用该缓存页就好。
如果没有,那就从 Free 链表中选一个空闲的缓存页,然后把磁盘中对应的页加载到该缓存页的位置。每当需要从磁盘中加载一个页面 Buffer Pool 中时,就从 Free 链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上,然后把该缓存页对应的 Free 链表节点从链表中移除,表示该缓存页已经被使用了,并且把该页写入 Lru 链表。
在初始化的时候,Buffer pool 中所有的页都是空闲页,需要读数据时,就会从 Free 链表中申请页,但是物理内存不可能无限增大,数据库的数据却是在不停增大的,所以 Free 链表的页是会用完的。
因此需要考虑把已经缓存的页从 Buffer pool 中删除一部分,进而需要考虑如何删除及删除哪些已经缓存的页。假设一共访问了 n 次页,那么被访问的页在缓存中的次数除以 n 就是缓存命中率,缓存命中率越高,和磁盘的 IO 交互也就越少 。
为了提高缓存命中率,InnoDB 在传统 Lru 算法的基础上做了优化,解决了两个问题:1、预读失效 2、缓存池污染 写操作
Buffer pool 另一个主要的功能是「加速写」,即当需要修改一个页面的时候,先将这个页面在缓冲池中进行修改,记下相关的重做日志,这个页面的修改就算已经完成了。
被修改的页面真正刷新到磁盘,这个是后台刷新线程来完成的。前面页面更新是在缓存池中先进行的,那它就和磁盘上的页不一致了,这样的缓存页被称为脏页(dirty page)。 问题:这些被修改的页面什么时候刷新到磁盘?以什么样的顺序刷新到磁盘?