专栏名称: 石杉的架构笔记
专注原创、用心雕琢!十余年BAT一线大厂架构经验倾囊相授
目录
相关文章推荐
传媒招聘那些事儿  ·  【简历提升】挖掘亮点:提升眼界思路,优化简历! ·  4 天前  
传媒招聘那些事儿  ·  【全职岗位表格】在线文档持续更新:新闻媒体/ ... ·  6 天前  
传媒招聘那些事儿  ·  小红书:直播公会运营 ·  5 天前  
51HR派  ·  京东又开始当鲶鱼了...... ·  3 天前  
51HR派  ·  5勺辣椒油惊动110 ·  3 天前  
51好读  ›  专栏  ›  石杉的架构笔记

面试官:能说一说Mysql缓存池吗?

石杉的架构笔记  · 公众号  ·  · 2021-01-09 17:21

正文


我的新课 《C2C 电商系统微服务架构120天实战训练营》 在公众号 儒猿技术窝 上线了,感兴趣的同学,可以长按扫描下方二维码了解课程详情:

课程大纲请参见文末

今天来聊一聊 Mysql 缓存池原理。

提纲附上,话不多说,直接干货。

前言

面试官:同学,你能说说Mysql 缓存池吗?

狂聊君:啊,这么难吗,容我组织一下语言。(内心OS:这TM还不简单?我能给你扯半小时!)

面试官:可以,给你一分钟时间想一想吧。

....一分钟后....

狂聊君:我准备好了,你可听好,我要开始表演了。

为什么要有缓存池?

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 链表节点都对应一个空闲的缓存页。

给大家画了个结构图:

这图怎么样,这下能看的懂了吧!

2、Lru 链表

Lru 链表用来管理已经读取的页,当数据库刚启动时,Lru 链表是空的,此时页也都放在 Free 列表中,当需要读取数据时,会从 Free 链表中申请一个页,把从放入到磁盘读取的数据放入到申请的页中,这个页的集合叫做 Lru 链表。

3、Flush 链表

Flush 链表用来管理被修改的页,Buffer Pool 中被修改的页也被称之为 「脏页」 ,脏页既存在于 Lru 链表中,也存在于 Flush 链表中,Flush 链表中存的是一个指向 Lru 链表中具体数据的指针。

因此只有 Lru 链表中的页第一次被修改时,对应的指针才会存入到 Flush 中,若之后再修改这个页,则是直接更新 Lru 链表中的页对应的数据。

这三者之间是这么个关系:

读操作

Buffer Pool 一个最主要的功能是 「加速读」 。加速读是当需要访问一个数据页面的时候,如果这个页面已经在缓存池中,那么就不再需要访问磁盘,直接从缓冲池中就能获取这个页面的内容。当我们需要访问某个页中的数据时,就会把该页加载到 Buffer Pool 中,如果该页已经在 Buffer Pool 中的话直接使用就可以了。

问题:那么如何快速查找在 Buffer Pool 中的页呢?

为了避免查询数据页时扫描 Lru,其实是根据表空间号 + 页号来定位一个页的,也就相当于表空间号 + 页号是一个 key,缓存页就是对应的 value。用表空间号 + 页号作为 key,缓存页作为 value 创建一个哈希表,在需要访问某个页的数据时,先从哈希表中根据表空间号 + 页号看看有没有对应的缓存页。







请到「今天看啥」查看全文