MySQL 存储引擎 InnoDB 内存结构之缓冲池
缓冲池是主存储器中的一个区域,在访问 table 和索引数据时InnoDB
会对其进行缓存。缓冲池允许直接从内存中访问频繁使用的数据,从而加快处理速度。在专用服务器上,通常将高达 80% 的物理内存分配给缓冲池。
为了高效处理大量读取操作,缓冲池被划分为可以容纳多行数据的页面。为了有效管理缓存,缓冲池被实现为页面的链接列表;通过 LRU(least recently used)算法的变体将很少使用的数据从缓存中淘汰出去。
了解如何利用缓冲池将频繁访问的数据保留在内存中是MySQL调优的重要方面之一。
缓冲池 LRU 算法
缓冲池使用一种最近最少使用(LRU)算法的变体作为列表进行管理。当需要空间以将新页面添加到缓冲池时,最近最少使用的页面会被移除,并将新页面添加到列表的中间。这种中点插入策略将列表视为两个子列表:
- 在前面是最近访问过的新("young")页面的子列表;
- 在尾部是最近较少被访问的旧("old")页面子列表。
缓冲池列表如下图所示:
该算法将频繁使用的页面保留在新页面子列表中。旧页面子列表则包含较少被使用的页面,这些页面是可能被淘汰(eviction)的候选页面。
默认情况下,算法运行如下:
- 缓冲池的 3/8 专门用于旧页面子列表。
- 列表的中点是新页面子列表的尾部与旧页面子列表的头部相遇的边界位置。
- 当
InnoDB
将一个页面读入缓冲池时,它最初会插入到中点位置(旧页面子列表的头部)。一个页面可以被读取,因为它是用户发起的操作(例如 SQL 查询)所必需的,或者是InnoDB
自动执行的预读(read-ahead)操作的一部分。 - 访问旧页面子列表中的一个页面会使其变为"young",并将其移动到新页面子列表的开头。如果页面由于用户发起的操作而被读取,则将立即进行首次访问,并且页面会被标记为"young"。如果页面是由于预读操作而被读取,则第一次访问不会立即发生,并且在该页面被淘汰之前可能根本不会发生。
- 随着数据库的运行,缓冲池中未被访问的页面会通过向列表的尾部移动而"老化"。新页面子列表和旧页面子列表中的页面都会随着其他页面的更新而老化。旧页面子列表中的页面也会随着在中点插入页面而老化。最终,一个长时间未被使用的页面会到达旧页面子列表的尾部并被淘汰。
默认情况下,通过查询读取的页面会立即移动到新页面子列表中,这意味着它们在缓冲池中停留的时间更长。例如,对于执行mysqldump操作或不带WHERE
子句的SELECT
语句进行的表扫描,可能会将大量数据带入缓冲池,并淘汰相同数量的较旧数据,即使新数据永远不会再次使用。同样地,由预读取后台线程加载且仅访问一次的页面会移动到新页面子列表的开头。这些情况会将频繁使用的页面推入旧页面子列表,使其面临淘汰的风险。关于优化这种行为的信息,请参阅"使缓冲池具有扫描抵抗力"和"配置 InnoDB 缓冲池预取(预读)"。
InnoDB
标准监视器(Standard Monitor)的输出在BUFFER POOL AND MEMORY
部分中包含了几个与缓冲池 LRU 算法操作有关的字段。有关详细信息,请参阅使用 InnoDB 标准监视器监控缓冲池。
缓冲区配置
您可以配置缓冲池的各个方面以提高性能。
- 理想情况下,您应该将缓冲池的大小设置为尽可能大的值,同时确保为服务器上的其他进程留有足够的内存,以避免过多的页面交换(paging)。缓冲池越大,
InnoDB
就更像是一个内存数据库,从磁盘读取一次数据,然后在后续读取从内存中访问数据。有关详细信息,请参阅"配置 InnoDB 缓冲池大小"。 - 在具有足够内存的64位系统上,可以将缓冲池分成多个部分,以最大程度地减少并发操作之间对内存结构的争用。有关详细信息,请参阅"配置多个缓冲池实例"。
- 您可以将频繁访问的数据保留在内存中,而不受会将大量不经常访问的数据带入缓冲池的操作突然活动的影响。有关详细信息,请参阅"使缓冲池具有扫描抵抗力"。
- 您可以控制何时以及如何执行预读请求,以异步方式将页面预取到缓冲池中,从而期望这些页面很快会被使用。有关详细信息,请参阅"配置 InnoDB 缓冲池预取(预读)"。
- 您可以控制何时进行后台刷新,以及是否根据工作负载动态调整刷新速率。有关详细信息,请参阅"配置缓冲池刷新"。
- 您可以配置
InnoDB
保存当前的缓冲池状态的方式,以避免服务器重新启动后的漫长预热时间。有关详细信息,请参阅"保存和恢复缓冲池状态"。
使用 InnoDB 标准监视器监控缓冲池
可以使用SHOW ENGINE INNODB STATUS访问InnoDB
标准监视器输出提供的有关缓冲池操作的指标。缓冲池指标位于InnoDB
标准监视器输出的BUFFER POOL AND MEMORY
部分:
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 2198863872
Dictionary memory allocated 776332
Buffer pool size 131072
Free buffers 124908
Database pages 5720
Old database pages 2071
Modified db pages 910
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4, not young 0
0.10 youngs/s, 0.00 non-youngs/s
Pages read 197, created 5523, written 5060
0.00 reads/s, 190.89 creates/s, 244.94 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not
0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read
ahead 0.00/s
LRU len: 5720, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
下表描述了InnoDB
标准监视器报告的缓冲池指标。
注:InnoDB
标准监视器输出中提供的每秒平均值是基于自上次打印InnoDB
标准监视器输出以来经过的时间计算的。
InnoDB 缓冲池指标如下表所示:
Name | Description |
---|---|
Total memory allocated | 为缓冲池分配的总内存(以字节为单位)。 |
Dictionary memory allocated | 为InnoDB 数据字典分配的总内存(以字节为单位)。 |
Buffer pool size | 分配给缓冲池的页面总大小。 |
Free buffers | 缓冲池空闲列表的页面总大小。 |
Database pages | 缓冲池 LRU 列表的页面总大小。 |
Old Database pages | 缓冲池旧 LRU 子列表的页面总大小。 |
Modified db pages | 当前在缓冲池中修改的页面数。 |
Pending reads | 等待读入缓冲池的缓冲池页面数。 |
Pending writes LRU | 从 LRU 列表底部等待写入缓冲池中旧脏页的数量。 |
Pending writes flush list | 检查点期间要刷新的缓冲池页面数。 |
Pending writes single page | 缓冲池中暂挂的独立页面写入数。 |
Pages made young | 缓冲池 LRU 列表中变年轻的页面总数(移至“新”页面的子列表的开头)。 |
Pages made not young | 缓冲池 LRU 列表中没有变年轻的页面总数(保留在“旧”页面子列表中没有年轻的页面)。 |
youngs/s | 在缓冲池 LRU 列表中,平均每秒访问旧页面并使其变为年轻页面的次数。有关更多信息,请参阅此表格后面的注释。 |
non-youngs/s | 在缓冲池 LRU 列表中,平均每秒访问旧页面并未导致页面变为年轻页面的次数。有关更多信息,请参阅此表格后面的注释。 |
Pages read | 从缓冲池读取的页面总数。 |
Pages created | 在缓冲池中创建的页面总数。 |
Pages written | 从缓冲池写入的页面总数。 |
reads/s | 平均每秒读取的缓冲池页面数。 |
creates/s | 平均每秒创建的缓冲池页面数。 |
writes/s | 平均每秒缓冲池页面写入数。 |
Buffer pool hit rate | 从缓冲池读取的页面与从磁盘存储读取的页面之间的缓冲池页面命中率。 |
young-making rate | 页面访问导致页面变为年轻页面的平均命中率。有关更多信息,请参阅此表格后面的注释。 |
not (young-making rate) | 页面访问未使页面变年轻的平均命中率。有关更多信息,请参见此表格后面的注释。 |
Pages read ahead | 平均每秒的预读操作次数。 |
Pages evicted without access | 平均每秒从缓冲池中淘汰而被访问的页面数量。 |
Random read ahead | 平均每秒随机预读操作次数。 |
LRU len | 缓冲池 LRU 列表的页面总大小。 |
unzip_LRU len | 缓冲池 unzip_LRU 列表的长度(以页面为单位)。 |
I/O sum | 访问的缓冲池 LRU 列表页面总数。 |
I/O cur | 当前间隔内访问的缓冲池 LRU 列表页面总数。 |
I/O unzip sum | 已访问的缓冲池 unzip_LRU 列表页面的总数。 |
I/O unzip cur | 当前时间间隔内已访问的缓冲池 unzip_LRU 列表页面的总数。 |
Notes:
-
年轻页面生成速率
youngs/s
指标仅适用于旧页面。它基于页面的访问次数而不是页面数计算。对于给定页面,可能会有多次访问,所有访问都会被计算在内。如果在没有进行大规模扫描的情况下youngs/s
非常低,则可能需要减少延迟时间或增加用于旧子列表的缓冲池百分比。增加百分比会使旧子列表变大,因此需要更长的时间才能将该子列表中的页面移动到尾部,从而增加这些页面再次被访问并成为年轻页面的可能性。请参阅“使缓冲池抗扫描”。 -
非年轻页面生成速率
non-youngs/s
指标仅适用于旧页面,它基于页面的访问次数而不是页面数计算。对于给定页面,可能会有多次访问,所有访问都会被计算在内。如果在执行大型表扫描(以及较高的youngs/s
)时没有看到更高的非年轻页面生成速率值non-youngs/s
,请增加延迟值。请参阅“使缓冲池抗扫描”。 -
年轻页面生成率
young-making
考虑了所有缓冲池页面的访问,而不仅仅是旧子列表中页面的访问。年轻页面生成率young-making
和非年轻页面生成率non-youngs/s
通常不会累加到整体缓冲池命中率上。在旧子列表中的页面命中会导致页面移动到新子列表,但是新子列表中的页面命中只有当它们距离列表头部一定距离时才会移动到列表头部。 -
非(年轻页面生成率)
not (young-making rate)
是指由于未达到由innodb_old_blocks_time定义的延迟时间,或由于新子列表中的页面命中未导致页面移动到头部,而导致页面访问未使页面变为年轻页面的平均命中率。此率考虑了所有缓冲池页面的访问,而不仅仅是旧子列表中页面的访问。
缓冲池服务器状态变量和INNODB_BUFFER_POOL_STATS表提供了许多与InnoDB
Standard Monitor 输出中相同的缓冲池指标。有关更多信息,请参阅示例“查询INNODB_BUFFER_POOL_STATS table”。
注:原文来自 MySQL 5.7 官方文档,阅读 MySQL 中文文档时有些语句理解不顺畅,便结合中文文档使用 ChatGPT 进行了翻译,如有不正请指出。