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文件中:

  1. Master Thread每1秒,将redo log buffer中的内容刷到磁盘redo log文件。
  2. 每个事务提交时,会将redo log buffer刷到磁盘redo log文件。
  3. 当redo log buffer剩余空间小于1/2时,将redo log buffer刷到磁盘redo log文件。

额外的内存池

在对一些数据结构本身的内存进行分配时,需要从额外的内存池中申请内存。当该区域内存不够时,会从buffer pool中申请内存。

例如:记录buffer pool的LRU、锁等信息的缓冲控制块对象,这个对象的内存需要从额外内存池申请。如果额外的内存池不够,从buffer pool中申请内存。

因此,如果申请了很大的 InnoDB 缓冲池时,也应考虑相应增加这个值。


参考资料:

《MySQL 实战45讲》

《从根上理解MySQL》

《MySQL技术内存 InnoDB 存储引擎》

《SQL 必知必会》


🎉 如果这篇文章对你有帮助,点赞👍 收藏⭐ 关注✅ 哦,创作不易,感谢!😀
请添加图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涛声依旧叭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值