Code前端首页关于Code前端联系我们

MySQL的数据存储设施——InnoDB缓冲池揭示

terry 2年前 (2023-09-26) 阅读数 45 #后端开发

设置大量内存的主要原因并不是因为它可以在内存中存储大量数据:最终目标是避免磁盘I/O,因为磁盘I/O O 比在内存中数据访问更慢。关键是平衡内存和磁盘大小、速度、成本和其他因素,为工作负载提供高性能。

MySQL需要为缓存分配更多的内存。它使用缓存来避免磁盘访问,这比内存中数据访问要慢。操作系统可能会存储一些数据,这对MySQL(尤其是MyISAM)有利,但MySQL也需要大量内存。

以下是我们在大多数情况下认为最重要的缓存:

  • InnoDB 缓冲池
  • 用于日志文件和 MyISAM 数据的 InnoDB 操作系统缓存
  • MyISAM查询缓存
  • MyISAM查询缓存 不能使用的缓存被配置手动,比如二进制日志操作系统缓存和表定义文件

接下来我们重点学习InnoDB存储池。

简介

InnoDB存储在磁盘上,其数据以页的形式进行管理。因此,InnoDB可以认为是基于系统盘的数据库。为了最大限度地减少 CPU 和磁盘速度之间的争用,基于磁盘的数据数据读取系统通常使用池技术存储来提高数据库的整体性能。

缓冲池基本上就是一块内存区域,没什么特别的。缓冲池(Buffer Pool)默认大小为128M,可以使用innodb_buffer_pool_size参数调整。

虽然大多数都是 InnoDB 表,但 InnoDB 存储池可能比其他任何东西都需要更多的内存。缓冲池的架构形象如下:

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

从图中可以看出,缓冲池中存储的数据页类型有: 索引页、数据页、取消页、插入缓冲区(插入缓冲区)、自适应哈希索引(adaptive hash index)、锁信息、数据字典信息等。

InnoDB还使用缓冲池来帮助延迟写入,允许多个写入操作组合起来同时写入。简而言之,InnoDB 严重依赖缓冲池,你必须确保给它足够的内存。但这并不意味着缓冲池的内存大小越大越好。如果数据量不大且数据增长缓慢,则无需为缓冲池分配过多的内存;如果数据量增长很快,可以预先安排缓冲池的大小。

运行以下命令查看缓冲池信息:

show engine innodb status;

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

转到“BUFFER POOL AND MEMORY”。相关字段的含义如下:

  • 分配的总主内存:这表示分配给 InnoDB 存储引擎的总内存大小(以字节为单位)。在此示例中,分配了 274,726,912 字节的内存。
  • 分配的字典内存:表示为InnoDB字典(例如表结构和索引信息)分配的内存量,以字节为单位。在此示例中,分配了 23,120,009 字节的内存。
  • 缓冲池大小:表示缓冲池的大小,代表用于存储数据页的内存量,以页为单位(通常为16KB)。在此示例中,缓冲池的大小为 16,384 页。
  • Buffer Free:表示当前缓冲池中空闲缓冲区的数量。在此示例中,有 1,018 个空闲缓冲区。
  • 它显示:这表示当前等待读取的存档页数。在此示例中,没有等待读取的数据页。
  • 暂停写入:LRU 0、flush list 0、single page 0:这表示等待写入磁盘的数据页数。在此示例中,没有等待写入的数据页。
  • Buffer Pool Hit Rate:缓冲池命中率,表示从缓冲池读取数据页时命中次数与读取总数的比值。在这个例子中,失败率为980/1000,即98%。

数据页

在Innodb的B+树中,我们最常说的节点叫做page(页面)。每个页面都存储用户数据,所有页面都是集成的。创建一棵 B+ 树。

Page是InnoDB存储引擎用来管理数据的最小磁盘单元。我们通常说每个节点是16KB,也就是说每个页面的大小是16KB。

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

Buffer Pool中,数据页也作为数据元素,存储了大量的数据。但我们通常称之为缓存页,因为Buffer Pool是一个缓冲池,里面的数据是从磁盘文件保存到内存中的。

缓冲池与磁盘之间数据交换的单位是数据页,包括从磁盘读取数据到缓冲池以及将池缓冲区中的数据逐出回磁盘,如图:​​

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

当MySQL服务启动和关闭时,常常需要预热和关闭缓冲池。接下来我们详细研究一下预热和收尾工作都做了什么。

预热

MySQL 5.6引入了数据库预热系统。 innodb_buffer_pool_dump_at_shutdown 和 innodb_buffer_pool_load_at_startup 两个参数控制抢占,但默认是禁用的,需要启用。 MySQL 5.7 默认启用。

InoDB Buffer Pool预热机制原理

1、关闭MySQL,将InnoDB Buffer Pool数据导出到文件中

  • 关闭MySQL时,将其保存到InnoDB Buffer Pool文件中。被按下。
  • ib_buffer_pool 是磁盘上存储 InnoDB 缓冲池中数据的文件名。它的名称和路径由innodb_buffer_pool_filename控制。该文件默认存储在InnoDB数据目录中。文件
  • ib_buffer_pool 存储表空间 ID 和页 ID。

启用“MySQL导出InnoDB Buffer Pool数据已准备就绪”功能。如果您需要永久运行它,请将其添加到 my.cnf 中。

SET GLOBAL innodb_buffer_pool_dump_at_shutdown=ON;

2、启动MySQL,执行InnoDB Buffer Pool中ib_buffer_pool文件的恢复

  • 根据ib_buffer_pool文件中的表空间ID和页ID恢复到InnoDB Buffer Pool。表空间ID和页面信息来自INNODB_BUFFER_PAGE_LRU表。文件
  • ib_buffer_pool 太旧且尚未关闭。 MySQL会比较新旧数据。如果磁盘上的页面最近执行过DML(例如更新),则ib_buffer_pool中的数据将不会加载到InnoDB Buffer Pool中。
  • 如果MySQL中的某些页面不再可用,下载系统将跳过该页面而不加载它。

运行“启动MySQL、InnoDB Buffer Pool历史数据导入”功能,建议直接添加到my.cnf中。

mysqld --innodb_buffer_pool_load_at_startup=ON;

3。在线保存和恢复InnoDB Buffer Pool数据

当MySQL运行时,InnoDB Buffer Pool数据可以保存到磁盘或恢复到InnoDB Buffer Pool。

SET GLOBAL innodb_buffer_pool_dump_now=ON;
SET GLOBAL innodb_buffer_pool_load_now=ON;

4。检查InnoDB Buffer Pool的备份和恢复进度状态。主要用于在线备份和恢复。

显示将 InnoDB Buffer Pool 数据保存到磁盘的进度状态

mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status';
+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status | Buffer pool(s) dump completed at 170112 17:26:02 |
+--------------------------------+--------------------------------------------------+
1 row in set (0.01 sec)

显示恢复 InnoDB Buffer Pool 数据的进度

mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_load_status';
+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_load_status | Buffer pool(s) load completed at 170112 17:31:22 |
+--------------------------------+--------------------------------------------------+
1 row in set (0.00 sec)

5.停止 InnoDB 缓冲池恢复操作

SET GLOBAL innodb_buffer_pool_load_abort=ON;

6。其他

如果MySQL版本低于MySQL 5.6,可以预热数据库。对经常使用的表执行枚举(*)操作会影响数据扫描。

select count(*) t1;
select count(*) t2;

对于以上参数,可以执行以下查询语句来查找当前值。

SELECT @@innodb_buffer_pool_dump_now;
SELECT @@innodb_buffer_pool_load_now;
SELECT @@innodb_buffer_pool_load_at_startup;
SELECT @@innodb_buffer_pool_dump_at_shutdown;

管理缓存页

缓冲池除了下载和导出数据页外,还需要管理数据页。 MySQL会使用free链表、Flush链表和LRU链表来管理缓冲池中的数据页。让我们来了解一下他们。

缓冲池中的页面不仅需要读取,还需要更改。修改后的页面必须出现在LRU链表中。当LRU链表中的某个页被修改时,该页被称为脏页,意思是缓冲池中的页数据与磁盘上的页数据不匹配。此时,数据库会使用扫描机制将脏页刷新回磁盘。刷新链表中的页是脏页。注意LRU链表和flush链表中都存在脏页。 LRU链表用于管理缓冲池中页面的可用性,flush链表用于管理将页面加载回磁盘。两者互不影响。下图展示了空闲链表、链表LRU和链表之间的关系:

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

空闲链表包含空闲缓存页

空闲链表,它是一个双链表,每个节点链表的一个是空闲缓存页对应的定义数据块。每个数据块有两个指针,一个是free_pre指针,另一个是free_next指针,分别指向前一个空闲链表节点和空闲链表节点的empty next。

空闲链接列表用于跟踪空闲数据页面,即没有数据的页面。这些页面可用于存储已读取或更改的新页面。

当需要从磁盘读取新的数据库页面时,MySQL 会从空闲链表中获取可用的空闲页面并将其添加到缓冲池中。

当InnoDB存储引擎启动时,其存储池为空,所有页面都在空闲列表中。由于所有的数据读写操作都需要先在缓冲池中完成,所以缓冲池的第一个工作就是从外部内存读取页面到缓冲池中,一般称为页面的物理读取(物理阅读)。

在高度并行的环境中,如果多个线程访问同一页,则只有第一个访问物理页的线程才能执行读操作。其他线程需要等待I/O操作,而MySQL受到并发控制的保护。

包括脏缓存页

Flush链表

Flush链表用于跟踪已经被修改但尚未写入磁盘的数据页,也称为脏页(Dirty Page)。

当缓冲池中的数据页被修改时,它们会被添加到 Flush 链表中,表明它们需要从磁盘上刷新,以保证数据的寿命。

链表通常使用链表数据库结构进行管理,每个节点代表一个脏页。这些节点包含指向下一个恶意页面的指针。

在后台或者适当的时候,MySQL会将Flush链表中的脏页写入磁盘,以保持数据的一致性和持久性。

链表LRU记录缓存页命中率

MySQL会使用LRU(最近最少使用)等算法来管理缓冲池中的数据页。如果缓冲池已满,MySQL可能会根据算法规则从内存中逐出以前未使用的数据页,为新数据的预读腾出空间。

InnoDB内存管理使用最近最少使用(LRU)算法。该算法的基础是剔除长期未使用的数据。

下图是LRU算法的基本模型。

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

InnoDB 的 Buffer Pool 管理的 LRU 算法是使用链表实现的。

  1. 上图中状态1时,链表头为P1,也就是说P1是最近访问过的数据页;
  2. 假设内存中只有这么多数据页;此时有请求读取P3的entry,所以变成状态2,P3移到前面;
  3. 状态3表示本次访问的数据页不存在于链表中,因此需要向Buffer Pool请求新的页Px,并将其添加到链表头中。但由于内存已满,新的内存无法执行。因此,链表末尾的数据页内存Pm将被存储,Px的内容将被存储,然后放置在链表的头部。
  4. 从角度来看,长时间没有被访问的Pm数据页已经被移除了。

如果每天对一个很少访问的200G历史数据表进行全表扫描,那么当前Buffer Pool中的所有数据都会按照上面的算法被删除,并且随着时间的推移会访问到Data page的内容。扫描将被保存。 。换句话说,Buffer Pool包含了这个历史数据表中的数据。

对于从事商业工作的图书馆来说,这并不好。你会发现Buffer Pool的内存容量明显下降,磁盘压力增大,SQL语句响应变慢。

因此,InnoDB无法直接使用这种LRU算法。因此,InnoDB对LRU算法进行了改进。

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

在InnoDB实现中,整个LRU链表按照5:3的比例划分为young区和old区。图中,LRU_old指的是old区域的第一个位置,是整个链表的5/8。也就是说,靠近链表顶部的5/8是年轻区,靠近链表底部的3/8是老区。

特殊:添加了中点位置。刚刚读完的页并不是直接放在LRU列的头部,而是放在距离末尾37%的位置。该算法称为中点插入策略

  • 中点占总列表的5/8
  • 中点之前的数据为新区(热点数据)
  • 中点之后的数据为非活跃数据,旧区。
  • 中点是新子列表尾部与旧子列表头部相交的边界

MySQL中的数据缓存利器——InnoDB缓冲池揭秘

查看中点

mysql> show variables like 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.04 sec)

运行LRU算法的过程如下。

  1. 上图状态1下,需要进入P3数据页。由于P3在young区域,所以它被移动到链表的头部,并且像优化前的LRU算法一样变成状态2。
  2. 之后,您需要输入一个当前不在链表中的新数据页。此时数据页Pm仍在被移除,但新插入的页Px被放置在LRU_old中。
  3. 旧区的数据页每次访问时必须进行如下判断:
    1. 如果该数据页在LRU链表中的时间超过1秒,则将其移至链表头部;
    2. 如果该数据页在LRU列表中的时间小于1秒,则位置不会改变。 1秒时间由参数innodb_old_blocks_time控制。默认值为 1000(以毫秒为单位)。

该策略旨在处理全表扫描等任务。我们以刚才查看的200G历史数据表为例。我们看一下改进后的LRU算法的工作逻辑:

  1. 在扫描过程中,最近应该加载的数据页被放置在old区域;
  2. A 数据页上有很多记录。该数据页将被多次访问。但由于是顺序扫描,这个数据页的第一个条目和最后一个条目之间的间隔不会超过1秒,所以仍然会被访问。住在老区;
  3. 如果继续查找下一个数据库,上一页的数据将不会被访问,所以你永远无法移动到链表的头部(即青年区),你也不会很快就会被取消。

事实证明,这个策略的主要优点是在检查这张大表的过程中,即使也使用了Buffer Pool,也完全不会影响young区,从而保证了需求率。缓冲池响应正常业务事务。

总结

InnoDB缓冲池是MySQL中的一项重要技术,在数据缓存方面发挥着重要作用。本文深入介绍了InnoDB存储池的基本概念,从缓冲池中存储数据页的结构到该机制的工作原理,预热,揭示其个体威力。除了加载和卸载数据页之外,缓冲池还使用Free Linked List、Flush Linked List和LRU Linked List来高效地管理数据页。作者:hresh
来源:稀土掘金

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门