16.缓冲池-BufferPool
buffer pool
常识:
- Buffer Pool
是一片
连续
的内存空间,通过innodb_buffer_pool_size
在服务器运行过程中调整buffer pool大小,默认为128MB Free Page
(空闲页),此页未被使用,位于Free 链表
;Clean Page
(干净页),此页已被使用,但是页面未发生修改
,位于LRU 链表
。Dirty Page
(脏页),表示此页已被使用
且已经被修改
,其数据和磁盘上的数据已经不一致。- 当
脏页
上的数据写入磁盘后,内存数据和磁盘数据一致,那么该页就变成了干净页
。脏页同时存在于LRU链表和Flush链表。 提高读性能
: 读取数据的时候,先从buffer pool LRU链表(干净页)读取数据,如果没有从磁盘读取并把它相邻的数据页一并加载进来。提高写性能
: 更新数据的时候,不需要每次都要写入磁盘,而是将 Buffer Pool 对应的缓存页标记为脏页,然后再由后台线程将脏页写入到磁盘
buffer pool的作用:
- 本质上就是个缓存池。为了降低磁盘和内存的IO差异,提高数据库的读写性能而设计
- 存储的数据包括:
索引页
数据页
undo页
插入缓冲区change buffer
自适应哈希索引
锁
其他
结构:
* 基本构成: * 控制块: *
存储信息:缓存页的表空间、页号、缓存页地址、链表节点 *
控制块跟缓存页
一一对应
* 缓存页: *
大小16KB,跟页的大小一致
管理空闲页:
- 如何查找空闲页?
- 当需要从磁盘中加载一个页到 Buffer Pool
中时,通过
Free链表
,取一个空闲的缓存页
,并且填写该缓存页对应的控制块的信息
,然后把该缓存页对应的控制块从Free 链表中移除
- 当需要从磁盘中加载一个页到 Buffer Pool
中时,通过
管理脏页:
- 如何查找脏页并写入磁盘?
- 在需要刷盘时,使用
Flush链表
,后台线程就可以遍历脏页
,写入到磁盘 脏页刷盘时机
:- 核心:既要保证缓存、磁盘的一致性,又要确保刷盘的效率
空闲时
后台线程定期将适量的脏页刷入到磁盘:- BUF_FLUSH_LRU方式:
定时
从LRU链表``尾部
扫描脏页
刷新到磁盘 - BUF_FLUSH_LIST方式:
定时
从Flush链表
刷新部分脏页
到磁盘
- BUF_FLUSH_LRU方式:
- Mysql
服务关闭前
,会把所有的脏页刷入到磁盘 Buffer Pool 空间不足
时,如果需要淘汰脏页,此时会先将脏页刷入磁盘,再进行淘汰redo log 日志满了
的情况下,会触发脏页刷入磁盘
- 核心:既要保证缓存、磁盘的一致性,又要确保刷盘的效率
缓存淘汰和命中率:
- 基于
LRU
(Least recently used) - 如何管理缓存页的过期,提高命中率
- 缓存相关的问题
- 避免预读导致的缓存失效
- 预读:在加载数据页时,为了减少磁盘IO,会提前把它相邻的数据页一并加载进来
- 失效:预读数据占据了缓存头部,一直未被访问,一些可能访问更频繁缓存反而被迫淘汰,导致失效
- 解决方案:
- 区分
预读数据
和真正被访问到的数据
,分别存到old区
和young区
- 进入
young
区新增条件:在 old 区域被真正访问过
- 区分
- 具体方法:
young区
:在LRU前部分,缓存时间更长,占63%old
区:在LRU后部分,缓存时间相对短,占37%- 通过
innodb_old_blocks_pct
设置young区和old区占比 - 这样如果预读的页就只加入到
old区域
的头部,当页被真正访问的时候,才将页插入 young 区域的头部,否则会被优先淘汰
- 避免Buffer Pool污染
- 污染的含义:扫描了大量的数据,导致全部页被替换,大量热数据被淘汰
- 触发:
- 结果集比较离散,大量的页被访问
- 全表扫描,所有页被访问
- 解决方案:
- 进入
young
区新增条件:在 old 区域停留时间超过 1 秒
- 在
控制块
中记录该页第一次被真正访问的时间
- 通过
innodb_old_blocks_time
设置停留时间
- 进入
- 具体方法:
- 当前访问时间与控制块中该页
第一次被访问的时间
相比- 大于1s, 则可以移动到
young
区域头部 - 小于1s,则继续停留在
old
区域
- 大于1s, 则可以移动到
- 当前访问时间与控制块中该页
- 其他优化:
- 防止
young
区域内部移动过于频繁- 处于
young
区域前1/4的数据被访问时,不会移动到头部 - 处于
young
区域后3/4的数据被访问时,移动到头部
- 处于
- 防止
- 避免预读导致的缓存失效
Buffer Pool的其他优化:
多实例:
- 在
Buffer Pool特别大
而且多线程并发访问
特别高的情况下,单一的Buffer Pool可能会影响请求的处理速度。(多线程下,访问Buffer Pool
中的各种链表都需要加锁
) - 多实例解决了锁的粒度问题
以chunk为单位申请内存:
- 在重新调整
Buffer Pool
大小的时候,需要重新向操作系统申请一块连续的内存空间,然后将旧的Buffer Pool
中的内容复制到这一块新空间,这个操作极其耗时 - 改为以
chunk
为单位向操作系统申请连续内存,这样一个Buffer Pool
实例其实是由若干个chunk
组成的。调整大小时,只需要申请新的chunk或者减少chunk即可 - 参数配置:
innodb_buffer_pool_chunk_size
,默认为128M。只能在服务器启动时指定,在服务器运行过程中是不可以修改的
Change buffer/Insert buffer(插入缓冲区)
- 作用:避免频繁磁盘IO
- 触发时机:
- 当对
非唯一索引
进行插入、更新或删除
操作时: - 如果相关的索引页不在
Buffer Pool
中,InnoDB会将这些操作
暂存在插入缓冲区
;等到系统有空闲资源时,InnoDB会将插入缓冲区中的操作应用
到Buffer Pool
中的索引页。 - 如果相关索引页在
Buffer Pool
中,则直接更新Buffer Pool
- 当对
- 注意点:只针对
非唯一索引
生效,原因:唯一索引有唯一性约束,必须要从磁盘取出数据才能校验唯一性是否正确;非唯一索引
没有这个限制,所以可以这么做
本文作者:navyum
本文链接:https://www.cnblogs.com/navyum/p/18509408
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步