InnoDB缓存模型
InnoDB缓存池
文章目录
缓存
根本目的
将我们需要的数据从访问速度比较慢的设备中,转移到访问比较快的设备里
CPU访问数据的速度对比
设备 | 访问速度 |
---|---|
流水线寄存器 | 一个时钟周期 |
L1 | 10个时钟周期以内 |
L2 | 20个时钟周期以内 |
L3 | 50个时钟周期以内 |
内存 | 纳秒级别 |
硬盘 | 微妙/毫秒级别 |
注:对L2和L3的访问速度估算来自互联网。内存的访问速度还需要考虑总线的速度和带宽。
从调用角度缓存池所处的位置
缓存池的数据结构
简单结构图
通过
innodb_buffer_pool_size
配置项,可以指定缓存池的大小。Mysql
在启动时得到的只是映射自内存的虚拟地址
,只有在真正加载数据页的时候,才会真正分配。
注:这里涉及到虚拟内存的问题,从操作系统角度看,内存的相关操作都是基于虚拟内存地址的。
缓存页和数据页
一般来说,
Buffer Pool
中的缓存页和数据页是一一对应的
默认情况下,磁盘存储的数据页大小为16KB
。对于Buffer Pool
中的数据页
通常叫做缓存页
,而Buffer Pool
在默认情况下,一个缓存页和一个磁盘上的数据页都是一一对应
的。
描述文件(Descriptive Data)
每一个缓存页都会有一个相对应的描述文件
描述文件中存储着缓存页的表空间
,数据页编号
,在Buffer Pool中的地址
等信息。Buffer Pool
中,描述文件会在缓存页面之前。描述数据的大小是缓存页大小的5%
左右,所以我们Buffer Pool
实际占用的内存空间会比申请的稍大一些。
缓存池如何维护数据信息
我们会对数据库进行很多CRUD操作,这些操作可能会导致数据页加载入缓存,可能导致数据更新,也可能导致缓存池满了,这些情况下
InnoDB
如何知晓还有没有缓存空闲,哪些数据被更新了,哪些数据页可以被淘汰?
如何快速定位到空闲的缓存空间(Free链表)
InnoDB使用一个双向链表追踪空闲的缓存空间
节点说明
-
base_node
是Free链表
的起点,head
和tail
分别指向这个链表中的首尾描述数据 - 每个空闲的描述数据都会维护一个
pre
和next
指针,分别指向上一个和下一个空闲的描述数据
-
base_node
中维护了一个当前可用的空闲总数
如何跟踪已经更新的数据
如果缓存中的数据有变更,我们就称缓存页是一个
脏页
。脏页
需要被不定时的刷新回磁盘,InnoDB是通过一个Flush的链表
跟踪脏页。和Free链表
类似
如何缓存不够用了,应该淘汰哪些缓存页
Mysql
的缓存池大小是有限的,不能一直往缓存池
里添加数据。当我们缓存不够用的时候,应该淘汰那些缓存命中率低的数据页。InnoDB
是通过一个LRU(Least Rencentyly Userd)
链表来区别每个缓存页的命中率
的。
普通的
LRU链表类似Free链表
LRU链表
- 除了空闲页,其它页都会在这个链表中
- 某个数据页被访问之后,会被插入到
链表的头部
- 如果需要淘汰数据页,从尾部开始进行淘汰
如何优化LRU链表
为什么要优化,普通的LRU链表有什么问题
-
预读
带来的问题Mysql
有预读机制,我们访问一个数据页的时候,在某些场景下,会把这个数据页相邻的数据页也加载到缓存中,此时有些数据页是不会被访问的,但是被插入到了LRU链表的首部,这是不科学的 - 频繁访问的数据页被淘汰的问题
假如有一个全表扫描的查询,数据量比较大,直接占满了内存,此时会导致LRU链表
中的数据全部被淘汰,包括那些被频繁访问的数据页
InnoDB是如何优化普通LRU链表的
冷热数据区域划分
冷数据区域的大小,由
innodb_old_blocks_pct
参数控制,默认37
数据页第一次加载入缓存的时候,会被放在冷数据区域的头部
什么时候冷数据中的数据页会插入到热数据区域
当数据被加载入冷数据区域,经过1000毫秒之后,数据被访问,就会放入到热数据首部
Tips:
- 数据被加载到缓存后,一般会立即被访问(要不然为什么会加载到缓存中),此时的访问并不能说明,该数据会被频繁访问
- 1000毫秒不是固定的,可以通过
innodb_old_blocks_time
参数控制
对于热数据区域的优化
特点:热数据区域中的缓存页从长时间来看,非常容易被访问到,此时如果
热数据
不在首部,会频繁的更改LRU链表的首部
,此时是没有必要的。
具体优化:
- 如果被访问的数据页位于热数据区域的
前1/4
,那么是不会去改变首部的 - 如果被访问的数据页位于热数据区域的
后3/4
,才会被移动到热数据首部
并发访问问题
数据库访问是一个非常频繁的操作,多个线程访问可以显著的提高性能,但是,上面也提到了,访问过程中涉及到操作描述数据,各种链表,多线程肯定要有相关的加锁操作,防止出现并发问题。
InnoDB的优化方式
将整个缓存池拆分称数个小的
Buffer Pool
,是不是有点分段锁的味道
缓存池动态扩容问题
我们的数据库随着业务发展,缓存池的大小肯定不是一成不变的,
InnoDB
通过chunk
的机制实现动态扩容
chunk
一个
Buffer Pool
中的描述数据和缓存页会被分配到多个chunk
中
当有新的内存空间被分配给缓存池的时候,那么就会被划分成多个chunk
,只需要把chunk和某个buffer pool
建立对应关系就行了
更多疑惑问题
-
多个Buffer Pool
缓存的数据页会有重复吗?它们之间有没有什么关系?