InnoDB 缓存池

作者 | 王亚楠

Hello, World!


1、为什么要使用缓存

根本目的

将我们需要的数据从访问速度比较慢的设备中,转移到访问比较快的设备里。

CPU 访问数据的速度对比
设备访问速度
流水线寄存器一个时钟周期
L110 个时钟周期以内
L220 个时钟周期以内
L350 个时钟周期以内
内存纳秒级别
硬盘微秒/毫秒级别

注:对 L2 和 L3 的访问速度估算来自互联网。内存的访问速度还需要考虑总线的速度和带宽。

从调用角度缓存池所处的位置

缓存池的数据结构

简单结构图

通过innodb_buffer_pool_size配置项,可以指定缓存池的大小。Mysql在启动时得到的只是映射自内存的虚拟地址,只有在真正加载数据页的时候,才会真正分配。

注:这里涉及到虚拟内存的问题,从操作系统角度看,内存的相关操作都是基于虚拟内存地址的。

缓存页和数据页

一般来说,Buffer Pool中的缓存页和数据页是一一对应的。

默认情况下,磁盘存储的数据页大小为16KB。对于Buffer Pool中的数据页通常叫做缓存页,而Buffer Pool在默认情况下,一个缓存页和一个磁盘上的数据页都是一一对应的。

描述文件(Descriptive Data)

每一个缓存页都会有一个相对应的描述文件。

描述文件中存储着缓存页的表空间,数据页编号,在 Buffer Pool 中的地址等信息。Buffer Pool中,描述文件会在缓存页面之前。描述数据的大小是缓存页大小的5%左右,所以我们Buffer Pool实际占用的内存空间会比申请的稍大一些。

2、缓存池如何维护数据信息

我们会对数据库进行很多 CRUD 操作,这些操作可能会导致数据页加载入缓存,可能导致数据更新,也可能导致缓存池满了,这些情况下InnoDB如何知晓还有没有缓存空闲,哪些数据被更新了,哪些数据页可以被淘汰?

如何快速定位到空闲的缓存空间(Free 链表)

InnoDB使用一个双向链表追踪空闲的缓存空间。

节点说明 base_nodeFree链表的起点,headtail分别指向这个链表中的首尾描述数据每个空闲的描述数据都会维护一个prenext指针,分别指向上一个和下一个空闲的描述数据 base_node中维护了一个当前可用的空闲总数。

如何跟踪已经更新的数据

如果缓存中的数据有变更,我们就称缓存页是一个脏页。脏页需要被不定时的刷新回磁盘,InnoDB 是通过一个Flush的链表跟踪脏页。和Free链表类似。

如何缓存不够用了,应该淘汰哪些缓存页

缓存命中率

Mysql的缓存池大小是有限的,不能一直往缓存池里添加数据。当我们缓存不够用的时候,应该淘汰那些缓存命中率低的数据页。InnoDB是通过一个LRU(Least Rencentyly Userd)链表来区别每个缓存页的命中率的。

普通的 LRU 链表类似 Free 链表

LRU 链表除了空闲页,其它页都会在这个链表中某个数据页被访问之后,会被插入到链表的头部如果需要淘汰数据页,从尾部开始进行淘汰。

3、LRU链表的黑科技

为什么要优化,普通的 LRU 链表有什么问题

  1. 预读带来的问题 Mysql有预读机制,我们访问一个数据页的时候,在某些场景下,会把这个数据页相邻的数据页也加载到缓存中,此时有些数据页是不会被访问的,但是被插入到了 LRU 链表的首部,这是不科学的。

  2. 频繁访问的数据页被淘汰的问题 假如有一个全表扫描的查询,数据量比较大,直接占满了内存,此时会导致LRU链表中的数据全部被淘汰,包括那些被频繁访问的数据库。

InnoDB 是如何优化普通 LRU 链表的

冷热数据区域划分

冷数据区域的大小,由innodb_old_blocks_pct参数控制,默认 37 数据页第一次加载入缓存的时候,会被放在冷数据区域的头部

什么时候冷数据中的数据页会插入到热数据区域

当数据被加载入冷数据区域,经过 1000 毫秒之后,数据被访问,就会放入到热数据首部

Tips:数据被加载到缓存后,一般会立即被访问(要不然为什么会加载到缓存中),此时的访问并不能说明,该数据会被频繁访问 1000 毫秒不是固定的,可以通过innodb_old_blocks_time参数控制

对于热数据区域的优化

特点:热数据区域中的缓存页从长时间来看,非常容易被访问到,此时如果热数据不在首部,会频繁的更改LRU链表的首部,此时是没有必要的。

具体优化:

1. 如果被访问的数据页位于热数据区域的前1/4,那么是不会去改变首部的。

2. 如果被访问的数据页位于热数据区域的后3/4,才会被移动到热数据首部。

4、并发访问问题

数据库访问是一个非常频繁的操作,多个线程访问可以显著的提高性能,但是,上面也提到了,访问过程中涉及到操作描述数据,各种链表,多线程肯定要有相关的加锁操作,防止出现并发问题。

InnoDB 的优化方式

将整个缓存池拆分成数个小的Buffer Pool,是不是有点分段锁的味道

5、缓存池动态扩容问题

我们的数据库随着业务发展,缓存池的大小肯定不是一成不变的,InnoDB通过chunk的机制实现动态扩容

chunk

一个Buffer Pool中的描述数据和缓存页会被分配到多个chunk中 

当有新的内存空间被分配给缓存池的时候,那么就会被划分成多个chunk,只需要chunk和某个buffer pool建立对应关系就行了

更多思考

多个Buffer Pool缓存的数据页会有重复吗?它们之间有没有什么关系?

全文完


以下文章您可能也会感兴趣:

我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值