InnoDB Buffer Pool 缓冲池详解
本文大纲
Buffer Pool 基础
缓冲池 Buffer Pool 的作用
InnoDB 存储引擎
是基于磁盘存储的。以页为单位存储数据。我们进行的增删改查操作本质上都是在操作数据页(包括读页、写页、创建新页)。由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库通常使用缓冲池来提高数据库的整体性能。
Buffer Pool
就是把磁盘上的页,缓存到内存中,用于降低与磁盘直接进行IO的成本。
InnoDB 引擎
在处理客户端请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中。之后就可以对页进行读写访问了。操作完页之后并不会立即释放掉其内存空间,而是将其缓存起来,将来有请求再次访问该页数据时,就可以省去磁盘IO的开销了。
Buffer Pool 缓存什么?
MySQL启动时, InnoDB 引擎向操作系统申请一块连续的内存空间,然后按照页的大小(默认16KB)划分出一个个空页,当磁盘上的页缓存到内存的 Buffer Pool
中会对空页进行填充。
缓冲池中的数据页类型有:数据页、索引页、插入缓冲(insert buffer)、自适应哈希索引、锁信息、数据字典信息等。数据页和索引页是缓冲池中的主要内容。
查看设置缓冲池大小
InnoDB 引擎通过 innodb_buffer_pool_size
变量查看缓冲池的大小。一般建议设置成可用物理内存的 60%~80%
mysql > show variables like 'innodb_buffer_pool_size'
通过 set global innodb_buffer_pool_size
修改缓冲池大小。
缓冲池的 预读 特性
缓冲池的作用就是提升IO效率,而在读取数据时存在“局部性原理”。也就是说我们使用了一些数据,大概率还会使用它周围的一些数据。
当数据页从磁盘加载到 Buffer Pool
中时,会把相邻的数据页也加载到 Buffer Pool
中,使用 预读 机制提前加载出来,可以减少未来可能的磁盘IO。
管理 Buffer Pool
数据页管理
Buffer Pool
中的页有以下几种状态:
1、空闲页:通过空闲页链表(Free List)管理。
2、已被使用的正常页:通过LRU链表(LRU List)管理。
3、已被使用的脏页:通过LRU链表和脏页链表(Flush List)管理。
脏页(dirty page):指缓冲池中被修改过的页,与磁盘上的数据页不一致。
Buffer Pool
中的空闲页通过空闲页链表管理,每当从磁盘中读取数据页缓存到Buffer Pool中时,从空闲页链表取一个空闲页使用,并从空闲链表中移除。
当需要将脏页刷盘时,遍历脏页链表,将脏页写入磁盘。
已被使用的正常页通过LRU链表管理,其中LRU链表上也有脏页。
缓冲池的大小是有限的,比如磁盘有50G,内存8G,而缓冲池只有1G。是无法将磁盘的数据全部放在缓冲池里,需要根据优先级来缓存数据,会优先对使用频次高的热数据进行加载。
你可以在 show engine innodb status
结果中,查看一个系统当前的 BP 命中率。一般情况下,一个稳定服务的线上系统,要保证响应时间符合要求的话,内存命中率要在 99% 以上。
执行 show engine innodb status
,可以看到Buffer pool hit rate
字样,显示的就是当前的命中率。比如 这个命中率,就是 99.0%。
基础 LRU 算法
InnoDB 内存管理用的是最近最少使用 (Least Recently Used, LRU) 算法,这个算法的核心就是淘汰最久未使用的数据。InnoDB 管理 Buffer Pool 的 LRU 算法,是用链表来实现的。
LRU链表:
链表头节点存储最近使用的数据,链表尾部节点存储最久未被使用的数据。淘汰时删除链表尾部节点。
LRU链表的基本操作:
1、state1状态:P1是最近使用的页,Pm是最久未被使用的页。
2、state2状态:当有读请求访问P3页,P3移动到head节点。
3、state3状态:假设Px页不在buffer pool中,而此时内存已经满了,需要先淘汰掉LRU尾结点(最久未被使用的Pm),再将Px放到head节点。
InnoDB 改进后的 LRU 算法
实际上, InnoDB 引擎对LRU算法做了改进。为什么没有直接使用基础LRU算法呢?
试想一下,如果要对一个很大的表做全表扫描,这个表是一个冷数据表(平时没有业务访问它)。如果使用基础的LRU算法,会 把Buffer Pool里的数据全部淘汰掉,存储的都是冷数据表的数据页。
这会导致Buffer Pool的内存命中率急剧下降,并且由于内存中没有热点数据,导致大量磁盘IO增加磁盘压力,SQL语句响应变慢。
InnoDB 引擎改进后的LRU算法如下:
InnoDB 将LRU链表按照5:3的比例分成了young区域
和old区域
。链表头部的5/8是young区
,链表尾部的3/8区域是old区域
。
old区域
占整个LRU链表的长度由参数innodb_old_blocks_pc
控制
1、当要访问P3页时,P3在young区直接移动到链表头结点。(state1 -> state2)
2、当要访问不存在Buffer Pool上的页时(Px),删除old区域尾结点Pm,但是与基础LRU算法不同的是,Px插入在old区域的“头结点”。
3、处于old区域的数据页,每次被访问到时,都需要做下面这个判断:
- 若这个数据页在LRU链表的时间超过了1秒,说明它是热点数据,将其移动到LRU链表young区域的头结点。
- 若这个数据页再LRU链表的时间小于1秒,则不做任何处理。
- 1秒这个时间是由参数
innodb_old_blocks_time
控制的,默认是1000ms
以上, InnoDB 改进后的LRU算法,在做全表扫描时,过程如下:
1、扫描过程中,需要新插入的数据页,都放到old区域
2、数据页中存储的是多条记录,因此这个数据页会被多次访问到,但由于是顺序扫描,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过 1 秒,因此这个数据页还是会被保留在 old 区域;
3、继续扫描后续的数据页,之前的这个数据页之后也不会再被访问到,它不会移动到young区域的链表头部,很快就会被淘汰出去。
改进后的LRU算法,即使再做大表全表扫描时,对LRU链表的young区域也没有影响,保证了Buffer Pool的命中率。
引申问题:全表扫描对server的影响
问题:如果对一个200G的做全表扫描,会不会把mysql服务器内存用光?
实际上mysql server并不会保存完整的结果集。MySQL是“边读边发”的。server读取的数据会写到net_buffer 内存块中,net_buffer的默认大小是16KB,内存块写满了就会将结果发送给客户端,客户端成功接收后,就会清空net_buffer,继续读取、发送数据。
多个Buffer Pool实例
Buffer pool 本质是 InnoDB 向操作系统申请一块连续的内存空间。
多线程并发情况访问,单一的Buffer pool 会影响请求的处理速度。因为涉及到竞争问题。所以在Buffer pool 很大的时候,将其拆分成若干个小的Buffer pool ,每个Buffer pool 都是一个实例,独立申请内存空间,独立管理数据。多线程并发访问时,不会互相影响,提高并发处理能力。
通过 Innodb_buffer_pool_instances
参数修改实例个数。通过innodb_buffer_pool_size
参数查看缓冲池的大小。
每个buffer pool 占用多少存储空间?
innodb_buffer_pool_size / Innodb_buffer_pool_instances
实例越多越好吗?
buffer pool实例不是越多越好,管理各个buffer pool也需要开销的。
InnoDB 规定:当innodb_buffer_pool_size
小于1G时,多个实例是无效的。 InnoDB 会默认把Innodb_buffer_pool_instances
设置为1.
Buffer Pool 刷盘机制
问题:SQL语句更新了缓冲池的数据,数据会马上同步到磁盘上吗?
当我们对数据库中的记录进行修改时,首先会修改缓冲池中页的记录信息。然后数据库会以一定的频率刷新到磁盘上。也就是checkpoint 机制。这样做的好处是提升数据库的整体性能。
当缓冲池不够用时,需要释放掉一些不常用的页,此时可以采用checkpoint机制,将不常用的脏页 回写到磁盘,然后再释放掉缓冲池中的内存。
脏页(dirty page):指缓冲池中被修改过的页,与磁盘上的数据页不一致。
下面几种情况会触发脏页的刷新:
- 当 redo log 日志满了的情况下,会主动触发脏页刷新到磁盘;
- Buffer Pool 空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;
- MySQL 认为空闲时,后台线程回定期将适量的脏页刷入到磁盘;
- MySQL 正常关闭之前,会把所有的脏页刷入到磁盘;
Buffer Pool 引申问题
问题1:如果buffer pool中数据修改成功,但是还没来得及刷盘,mysql宕机了。数据没有持久化,怎么办?
Redo Log
InnoDB 的更新操作采用的是 Write Ahead Log 策略,即先写日志,再写入磁盘,通过 redo log 日志让 MySQL 拥有了崩溃恢复能力。
问题2:数据更新到一半断电了,要回滚到更新之前的版本,怎么办? Undo Log
其他的内存部件
InnoDB引擎的内存区域,除了缓冲池buffer pool外,还有重做日志缓冲和额外的内存池。
重做日志缓冲 redo log buffer
InnoDB先将redo log放到重做日志缓冲区,然后按照一定的频率将其刷到重做日志文件。
缓冲区大小由innodb_log_buffer_size
控制,默认是8MB。
以下3种情况会将redo log buffer中的内容刷到磁盘的redo log文件中:
- Master Thread每1秒,将redo log buffer中的内容刷到磁盘redo log文件。
- 每个事务提交时,会将redo log buffer刷到磁盘redo log文件。
- 当redo log buffer剩余空间小于1/2时,将redo log buffer刷到磁盘redo log文件。
额外的内存池
在对一些数据结构本身的内存进行分配时,需要从额外的内存池中申请内存。当该区域内存不够时,会从buffer pool中申请内存。
例如:记录buffer pool的LRU、锁等信息的缓冲控制块对象,这个对象的内存需要从额外内存池申请。如果额外的内存池不够,从buffer pool中申请内存。
因此,如果申请了很大的 InnoDB 缓冲池时,也应考虑相应增加这个值。
参考资料:
《MySQL 实战45讲》
《从根上理解MySQL》
《MySQL技术内存 InnoDB 存储引擎》
《SQL 必知必会》
文章目录 InnoDB Buffer Pool是什么? 我们的数据是如何放在InnoDB Buffer Pool中的? InnoDB怎么知道数据页是否在Buffer Pool中? InnoDB Buf ... MySQL · 性能优化· InnoDB buffer pool flush策略漫谈 背景 我们知道InnoDB使用buffer pool来缓存从磁盘读取到内存的数据页.buffer pool通常由数 ... 前言 用户对数据库的最基本要求就是能高效的读取和存储数据,但是读写数据都涉及到与低速的设备交互,为了弥补两者之间的速度差异,所有数据库都有缓存池,用来管理相应的数据页,提高数据库的效率,当然也因为引入 ... 最近在对公司的 MySQL 服务器做性能优化, 一直对 innodb 的内存使用方式不是很清楚, 乘这机会做点总结. 在配置 MySQL 的时候, 一般都会需要设置 innodb_buffer_poo ... 点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 王航威 来源 | 公众号「yangyidba」 ... 14.6.3.1 The InnoDB Buffer PoolInnoDB 保持一个存储区域被称为buffer pool 用于cache数据和索引在内存里,知道InnoDB buffer pool 如 ... 14.4.3.5 Configuring InnoDB Buffer Pool Flushing 配置InnoDB Buffer Pool 刷新:InnoDB执行某些任务在后台, 包括flush 脏数 ... 转载地址:Go 语言 bytes.Buffer 源码详解之1 - lifelmy的博客 前言 前面一篇文章 Go语言 strings.Reader 源码详解,我们对 strings 包中的 Reade ... 一.Innodb 架构图 1.整体架构图-内存结构 & 磁盘结构 二.Buffer Pool 缓存池 1. 缓存池实现 采用页面链表表(划分可能容纳多行的页) + 中点插入策略(新增加的页插入 ... 因工作需要,学习了C#方面的知识,看到Buffer.BlockCopy方法.从微软平台看到的解释第一时间没有看懂,感觉是其中的例子详解不充分.自己推理了一下,如下分享我自己的理解过程:(初学者欢迎查正 ...InnoDB Buffer Pool 缓冲池详解相关推荐
最新文章
热门文章