前言
面试官:同学,你能说说Mysql 缓存池吗?
是CPP啊:啊,这么难吗,容我组织一下语言。(内心OS:这TM还不简单?我能给你扯半小时!)
面试官:可以,给你一分钟时间想一想吧。
....一分钟后....
是CPP啊:我准备好了,你可听好,我要开始表演了。
为什么要有缓存池?
MySQL 的 innodb 存储引擎是基于磁盘存储的,并且是按照页的方式进行管理的。
在数据库系统中,CPU 速度与磁盘速度之间的差距是非常大的,为了最大可能的弥补之间的差距,提出了缓存池的概念。
所以缓存池,简单来说就是一块「内存区域」,通过内存的速度来弥补磁盘速度较慢,导致对数据库造成性能的影响。
缓存池的基本原理
「读操作」:
在数据库中进行读取页的操作,首先把从磁盘读到的页存放在缓存池中,下一次读取相同的页时,首先判断该页是不是在缓存池中。
若在,称该页在缓存池中被命中,则直接读取该页,否则,还是去读取磁盘上的页。
「写操作」:
对于数据库中页的修改操作,首先修改在缓存池中的页,然后在以一定的频率刷新到磁盘,并不是每次页发生改变就刷新回磁盘,而是通过 checkpoint 的机制把页刷新回磁盘。
可以看到,无论是读操作还是写操纵,都是对缓存池进行操作,而不是直接对磁盘进行操纵。
缓存池结构
Buffer Pool 是一片连续的内存空间,innodb 存储引擎是通过页的方式对这块内存进行管理的。
缓存池的结构如下图:
可以看到缓存池中包括数据页、索引页、插入缓存、自适应哈希索引、锁信息、数据字段。
其中数据页和索引页会用掉多数内存。
「但是,innodb 是如何管理缓存池中的这么多页呢?」
为了更好管理这些缓存的页,innodb 为每一个缓存页都创建了一些所谓的控制信息,这些控制信息包括该页所属的:
表空间编号(sapce id)
页号(page numeber)
页 在 buffer Pool 的地址
一些锁信息以及 LSN 信息日志序列号
其他控制信息
每个缓存页对应的控制信息占用的内存大小是相同的,我们把每个页对应的控制信息占用的一块内存称为一个「控制块」。
「控制块」和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前边,缓存页被存放到 Buffer Pool 的后边。
Buffer Pool 对应的内存空间示意图:
缓存池参数设置
innodb_buffer_pool_size:缓存池的大小最多应设置为物理内存的 80%
innodb_buffer_pool_instance:设置有多少个缓存池,通常建议把缓存池个数设置为 CPU 的个数,多个缓存池可以减少数据库内部的资源竞争,增加数据库并发访问的能力
innodb_old_blocks_pct:老生代占整个 LRU 的链长比例,默认是 3:7
innodb_old_blocks_time:老生代停留时间窗口,单位是毫秒,默认是 1000,即同时满足“被访问”与“在老生代停留时间超过 1 秒”两个条件,才会被插入到新生代头部
缓存池管理
「管理缓存池依赖的链表结构」:
Free 链表
当启动 Mysql 服务器的时候,需要完成对 Buffer Pool 的初始化过程,即分配 Buffer Pool 的内存空间,把它划分为若干对控制块和缓存页,但是此时并没有真正的磁盘页被缓存到 Buffer Pool 中,之后随着程序的运行,会不断的有磁盘上的页被缓存到 Buffer Pool 中。
在使用过程中,为了记录哪些缓存页是可用的,我们把所有空闲的页包装成一个节点组成一个链表,这个链表可以称作为 Free 链表(空闲链表)。因为刚刚完成初始化的 Buffer Pool 中所有的缓存页都是空闲的,所以每一个缓存页都会被加入到 Free 链表中。
为了方便管理 Free 链表,特意为这个链表定义了一些「控制信息」,里面包含链表的头节点地址,尾节点地址,以及当前链表中节点的数量等信息。
另外会在每个 Free 链表的节点中都记录了某个「缓存页控制块」的地址,而每个「缓存页控制块」都记录着对应的「缓存页地址」,所以相当于每个 Free 链表节点都对应一个空闲的缓存页。.........