这是学习笔记的第 1894 篇文章
在开始这部分内容之前,我们需要理清buffer和cache的差别,因为在数据库层面会有大量的buffer和cache的术语,我们在学习的时候非常容易混淆。
Buffer的本意是缓冲,cache是缓存,计算机术语里面有buffer cache, page cache,和数据库里的含义是相似的。
计算机领域中处理磁盘IO读写的时候,基于cpu,memory,disk有这样一个示意图:
其中page cache是文件系统层面的缓存,数据库层面最直观的就是第一次查询数据的时候会慢一些,之后就会快得多,整个过程是把磁盘里的数据加载到这个缓存里面。
另外一部分是buffer cache,其实指的是磁盘等块设备的缓冲,比如内存里的数据要写入磁盘文件,是一个异步的过程,而且为了防止断电丢失数据库,会按照一定的策略把数据刷新落盘。如果结合最开始的InnoDB体系结构图,其实整体要表达的含义是类似的。
怎么理解MySQL里面的缓存池管理呢,我们可以先使用show engine innodb status看一下缓冲池和内存的输出内容,按照关键字“BUFFER POOL AND MEMORY”查看,输出如下:
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 33533460480 #由innodb分配的总内存为32G
Dictionary memory allocated 14596467
Buffer pool size 1965840 #缓冲池分配的页数
Free buffers 1633878 #缓冲池空闲页数
Database pages 326446 #LRU列表中分配的数据页数,包含young sublist和old sublist
Old database pages 120340 #LRU中的old sublist部分页的数量
Modified db pages 0 #脏页的数量
Pending reads 0 #挂起读的数量
Pending writes: LRU 0, flush list 0, single page 0 #挂起写的数量
Pages made young 9, not young 0 #LRU列表中页移动到LRU首部的次数,因为该服务器在运行阶段改变没有达到innodb_old_blocks_time阀值的值,因此not young为0
0.00 youngs/s, 0.00 non-youngs/s #表示每秒young和non-youngs这两类操作的次数
如果理解了上面的输入含义,也就基本理解了缓冲池的一些基本含义。
这里要隆重介绍下InnoDB里的LRU技术,也是在数据库的缓存设计中都会使用的算法。
LRU本质是尽可能让数据页在缓存中存在,提高访问效率,但是缓存是有限的,怎么能够减少重复的页加载频率呢,InnoDB的LRU是一种定制化的算法,首先它会有一个列表,我们叫LRU LIST,上面存放了一些数据页,这里就是Database pages 326446 ,大约是5G左右,除此之外可用的也页为:Free buffers 1633878 ,大约是25G左右,如果你比较细心,拿出笔算一下,其实会发现Free buffers +Database pages的值和Buffer pool size的大小是不相等的,其实还有其他的一些其他缓冲池的页被分配利用,比如自适应哈希索引,Lock信息等,它们的管理不是基于LRU的。
回到LRU算法,InnoDB在LRU列表中加入了参考点,也叫midpoint。传统的LRU算法当访问到的页不在缓冲区是直接将磁盘页数据调到缓冲区队列;而InnoDB并不是直接插入到缓冲区队列的队头,而是插入LRU列表的midpoint位置。这个算法称之为midpoint insertion stategy。默认配置插入到列表长度的5/8处,和数学中的黄金分割(0.618)很接近,midpoint由参数innodb_old_blocks_pct控制,我们来简单验算验证一下,可以看到是很接近的值:
mysql> select 5/8,1-120340/326446 ,100-@@innodb_old_blocks_pct;
+--------+-----------------+-----------------------------+
| 5/8 | 1-120340/326446 | 100-@@innodb_old_blocks_pct |
+--------+-----------------+-----------------------------+
| 0.6250 | 0.6314 | 63 |
+--------+-----------------+-----------------------------+
midpoint之前的列表称之为new列表,也叫young sublist或者sublist of new block区域,里面的数据可以理解为热数据。
之后的列表称之为old列表,也叫old sublist或者sublist of old block区域,它们的关系可以参考如下图所示。
但是有了参照点怎么有效的管理呢,一些全表扫描的表如果进入sublist of new block区域,整个LRU就会是性能的瓶颈了,而且mid位置的页也不是永久的,这种情况也叫缓存污染。为了解决这个问题,InnoDB存储引擎引入了innodb_old_blocks_time来表示页读取到mid位置之后需要等待多久才会被加入到LRU列表的热端。可以通过设置该参数保证热点数据不轻易被刷出,这个参数值默认为1000(毫秒)。
所以这个时候反过来看”BUFFER POOL AND MEMORY”部分的输出就不难理解了,如果你在线上环境查看InnoDB的状态输出信息,会看到有多个BUFFER POOL的输出,BUFFER POOL会从0开始,
----------------------
INDIVIDUAL BUFFER POOL INFO
----------------------
---BUFFER POOL 0
Buffer pool size 245730
Buffer pool size, bytes 0
Free buffers 204625
Database pages 40414
Old database pages 14898
Modified db pages 0
Pending reads 0
这个是通过参数 innodb_buffer_pool_instances开启了多个缓存池,把需要的数据页可以通过hash算法指向不同的缓存池里面,可以进行并行的内存读写,在高IO负载的情况下性能提升明显。
前面熟悉了InnoDB对于LRU的管理方式之后,有些同学可能有些迷茫,说还有FLUSH LIST,FREELIST这些和LRU LIST是什么关系呢,很多同学从入门到放弃就是因为这样的而一些关联关系没搞明白。
我们在InnoDB status里面输出的内容:
Free buffers 204625
其实这个是由FREE LIST来维护的。
对于脏页的管理,InnoDB有一个专门的列表FLUSH LIST,它的大小不是无限大或者动态的,在MySQL 5.6中引入了新参数innodb_lru_scan_depth来控制LRU列表中可用页数量,默认值为1000,即16M,它会影响现成Page Cleaner 刷新脏页的数量,从使用率和性能来说,不是越大越好。
为什么会需要FLUSH LIST来维护脏页的数量呢,主要目的是使InnoDB尽可能保持一个较新的状态,在系统崩溃之后能够快速的恢复,这个对于数据状态的记录中是通过Checkpoint LSN来维护的,我们下一小节会细说Checkpoint的技术。
而对于脏页的刷新比例,是由参数innodb_max_dirty_pages_pact来控制的(默认是75,而根据谷歌的压测推荐是80)
这几个LIST之间的关系类似于这样的形式:
其中buffer pool中的最小单位是页,分为三种类型
1) free page :此page未被使用,此种类型page位于FREE LIST中
2) clean page:此page被使用,对应数据文件中的一个页面,但是页面没有被修改,此种类型page位于LRU LIST中
3) dirty page:此page被使用,对应数据文件中的一个页面,但是页面被修改过,此种类型page位于LRU LIST和FLUSH LIST中
如果要查看Page的一些状态数据,可以使用如下的命令:
mysql> show global status like '%buffer_pool_pages%';
+-----------------------------------------+------------+
| Variable_name | Value |
+-----------------------------------------+------------+
| Innodb_buffer_pool_pages_data | 254103 |
| Innodb_buffer_pool_pages_dirty | 3340 |
| Innodb_buffer_pool_pages_flushed | 270022533 |
| Innodb_buffer_pool_pages_free | 7998 |
| Innodb_buffer_pool_pages_LRU_flushed | 0 |
| Innodb_buffer_pool_pages_made_not_young | 6324461464 |
| Innodb_buffer_pool_pages_made_young | 424446968 |
| Innodb_buffer_pool_pages_misc | 11 |
| Innodb_buffer_pool_pages_old | 93638 |
| Innodb_buffer_pool_pages_total | 262112 |
+-----------------------------------------+------------+
隔几秒钟再去查看,会发现页的数量就会有很明显的变化。
其中,脏页的比率计算可以参考如下的公式:
缓存池中的页就是在这三种状态中进行变换和调整,总体来说,FLUSH LIST是一种定量的管理方式,追求多快好省,而FREE LIST和LRU LIST是一种动态平衡的状态,大小要远远高于FLUSH LIST.
,