15.5.1 缓冲池
原文地址:https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html
缓冲池是一个存在内存中用来缓存被访问了的表和索引数据的区域。缓冲池可以让频繁访问的数据直接从内存读取,从而加速处理。在独立部署的数据库服务器上,高达80%的物理内存常常都分配给缓冲池。
为了高效的执行大数据量的读取操作,缓冲池被分成能够容纳多行数据的一个个页。为了高效的管理缓存,缓冲池用链表页来实现;很少使用的数据会被一LRU算法的变种淘汰出缓存。
知道如何利用缓冲池技术来让频繁访问的数据保存在内存中是MySQL调优的一个重要方向。
缓冲池LRU算法
缓冲池被当做一个使用了LRU(least recently used)算法的一个变种的链表来管理。当缓冲池需要空间来添加一个新的页时,最少使用的页会被淘汰,然后新的页会被加到链表的中间。这种中间插入策略把链接一分为二当做两个子链表来对待:
- 在链表头部,是一个存储最近较多被访问数据的子链表。
- 在链表尾部,是一个存储最近较少被访问数据的子链表。
图表15.2 缓冲池链表
此算法把频繁访问的数据页存储在新子链表中。老子链表保存较少使用的数据页;这些页是潜在的淘汰对象。 此算法按如下方式操作:
- 3/8的缓冲池空间专门用于旧缓冲池。
- 新子链表的尾部连接着旧子链表的头部,这个链表的中间点是新旧链表的分界线。
- InnoDB存储引擎每当读取一个页数据到缓冲池内,一开始会把页插入到中间点(即旧子链表的头部)。当一个类似于SQL查询的由用户指定的操作,或者是由InnoDB自动发起的预读取操作需要时,这个页数据将会被读出。
- 访问一个在旧子链表的页数据时将会让数据变得更“新”,并把它移动到缓冲池的头部(同时时新子链表的头部)。如果一个页数据因为需要被读取,首次访问会立即出现并让页数据变‘新’。如果这个页是由于预读取被读到,首次访问可能不会立即出现(也许当这个页从缓冲池消失都还没被读取。)
- 正如数据库所执行的,缓存池中不被访问的页面会向链表尾部移动。在新老子链表中的数据都会因为其他被读取的页面而变得更‘老’,除此之外,旧子链表中的数据还会因为新页插入到中间点而变‘老’,最终,一个一直不被访问的页将会到达旧子链表的尾端而被淘汰出缓存池。
通常,因查询而被读取的页会移动到新子链表,意味着它们会在缓存池中存活更久。表扫描(例如执行mysqldump操作,或者执行一个没有WHERE子句的SELECT查询)会把大量的数据写入到缓存池中,导致等量的较老的数据从缓存池中删除,即使这批新数据以后再也不会被访问到。类似的,通过后台预读取线程家的页数据,随后仅仅被访问了一次然后移动到新子链表的头部。这样的情况下会导致高频访问的页移动到旧子链表并遭到淘汰。想获取这些情况下的优化方法,请查看第15.8.3.3节 如何避免缓存池扫描的副作用
和第 15.8.3.4节 配置InnoDB缓存池的预读取(Read-Ahead)
。
InnoDB的标准监控输出BUFFER POOL AND MEMORY
段中包含几个和buffer bool LRU算法相关的字段。详细请查看文档 Monitoring the Buffer Pool Using the InnoDB Standard Monitor
的内容。
缓存池配制
你可以配置缓存池的多个方面来提升性能。
如果可能,在留够服务器上其他进程的内存的前提下,你可以按实际情况分配尽可能大的内存给缓存池。缓存池内存越大,InnoDB运行的就更像一个内存数据库,从磁盘读取一次数据后,随后访问该数据则直接从内存中读取。具体请查阅
第15.8.3.1节 配置InnoDB的缓存池
。在拥有足够内存的64位操作系统上,你可以把缓存池拆成多分来避免数据库并发操作时对内存结构的竞争。详情请查看
第15.8.3.2节 配置多个缓存池实例
。你可以将频繁访问的数据放入内存中,而不用担心突然出现的操作会把大量不常访问的数据带到缓存池中。详情请查看
第15.8.3.3节 消除缓存池扫描的副作用
当你预料到一个页马上将被用到时,你可以控制什么时候并且怎样来执行一个预读操作来吧页面数据异步加载到缓存池中。详情请查阅
第15.8.3.4 配置缓存池预读取
。你可以控制后台刷新的时机和设置刷新的频率是否随负载动态调整。详情请阅读
第15.8.3.5节 配置InnoDB刷新
。你可以配置InnoDB如何来保存当前的缓存池状态以避免服务器重启之后需要较长时间的热备。详情请查看
第15.8.3.7节 保存和恢复缓存池状态
。
使用InnoDB标准监控组件来监控缓存池。
InnoDB标准监控组件的输出展示了缓存池运行的相关指标,这个信息可以通过 SHOW ENGINE INNODB STATUS
命令来查看。缓存池相关的指标位于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标准监控器输出的主要指标的含义。
NOTE
InnoDB标准监控器输出的每秒平均值基于上次打印时间到当前时间所经过的时间计算。
表格15.2 缓存池指标
名称 | 描述 |
---|---|
Total memory allocated | 缓存池分配的总内存(字节为单位) |
Dictionary memory allocated | 为InnoDB数据字典分配的内存(字节为单位) |
Buffer pool size | 分配个缓存池的页总大小 |
Free buffers | 缓存池空闲链表页大小 |
Database pages | 缓存池LRU链表页大小 |
Old database pages | 缓存池旧子链表页大小 |
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 | 每秒让旧数据重新变年轻的访问次数。更多信息请看表后的备注。 |
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 | 随机预读取操作平均每秒次数 |
unzip_LRU len | 缓存池 unzip_LRU 链表页总大小 |
I/O sum | 最近50s缓存池LRU链表页访问总次数 |
I/O cur | 缓存池LRU链表页访问总次数 |
I/O unzip sum | 最近50s缓存池unzip_LRU链表页访问总次数 |
I/O unzip cur | 缓存池unzip_LRU链表页访问总次数 |
Notes
youngs/s
指标仅仅适用老的页。它基于对页的访问次数而不是页的数量。对于同一个页的每一次访问都计算在内,如果你看到非常低的youngs/s
值并且当前没有大数据量的扫描,你可能需要降低查看的间隔时间或者需要提高旧子链表所占空间的比率。增加旧子链表空间所占的比率越大,在旧子链表里的数据就会越晚移动到链表尾部,这会提高这些页面被访问并重新变新的概率。non-youngs/s
指标仅仅适用于老的页面。它是基于页面的总访问次数而不是页面数来统计的。碎玉同一个页面的每次访问都被计算在内。在你执行了了一个大数据量的查询之后没有看到一个较高的non-youngs/s
值时(并且较高的youngs/s
的值),那么你需要提高查看的间隔时间。返新率统计所有对缓存池页面的访问而不仅仅是旧子链表中的页面。返新率和非返新率不是通常相加得到整体的缓存池命中率。命中旧子链表中的页面会让它移动到新子链表,但是在新子链表中命中的页面仅仅当它们不在链表头部时才会移动到链表的头部。
非返新率是计算页面访问没让页面返新的概率,导致页面没有返新的原因可能是由于
innodb_old_blocks_time
指定的延迟时间没到,或者是由于在新子链表的页命中但没有移动到链表头部。这个比率计算对所有缓存池页面的访问,而不仅仅是对旧子链表页面的访问。
缓存池server status variables
和 INNODB_BUFFER_POOL_STATS
表提供了很多与InnoDB标准监控输出相同的指标。详情请查看第15.10节 查询INNODB_BUFFER_POOL_STATS表
。