目录
- 1、背景
- 2、Buffer Pool
- 【1】含义
- 【2】组成
- 【3】free链表
- 【4】哈希查找缓存页
- 【5】flush链表
- 【6】LRU链表
- 【7】刷新脏页到磁盘
- 【8】Buffer Pool实例
- 【9】chunk
- 【10】Buffer Pool状态信息
- 3、总结
1、背景
mysql数据是存储在磁盘上的,但是从磁盘上读取数据的速度太慢,所以就需要把数据从磁盘读取在内存中,申请的这块内存就叫Buffer Pool,也就是缓存池的意思,接下来我们就来讲一下Buffer Pool。
2、Buffer Pool
【1】含义
在mysql服务启动时会申请一块存储页的连续内存,这快内存就叫Buffer Pool,Buffer Pool的大小可以通过配置文件指定,linux上的配置文件为my.cnf,windows上的配置文件为my.ini,windows上的默认配置如下,可以根据内存大小进行修改,最小值为5M:
cat my.ini | grep 'innodb_buffer_pool_size'
innodb_buffer_pool_size=128M
【2】组成
Buffer Pool是由控制块和缓存页组成,一个控制块对应一个缓存页,控制块包含对应缓存页的表空间编号、页号、缓存页在Buffer Pool中的地址、链表节点信息、锁、LSN信息, innodb_buffer_pool_size的大小并不包含控制块的空间,所以再给Buffer Pool申请一块连续的内存时会额外申请5%的空间用于存储控制块,其组成图如下:
如果申请的连续内存剩余空间不足以方法一个控制块和对应的缓存页,这个剩余的空间就叫碎片,如果刚好能放下控制块和对应的缓存,碎片就不存在。
【3】free链表
从磁盘读数据写到Buffer Pool中时,需要从Buffer Pool中找到空闲的缓存页去写入,为了方便快速找到哪些缓存页是空闲的,就有了free链表,也就是所有空闲缓存页对应的控制块组成的链表,其组成图如下:
free链表由链表基节点和控制块组成,链表基节点是额外的空间,不属于Buffer Pool,链表基节点存储了空闲缓存页对应的控制块的首地址和尾地址,还有空闲缓存页的个数,所有空闲缓存页对应的控制块组成一个双向链表。
通过free链表就可以很方便从中取出一个空闲缓存页,将其对应的控制块填入对应信息,然后从free链表中移除,再写数据到缓存页。
【4】哈希查找缓存页
查找数据时需要判断数据所在的页是否在Buffer Pool上,为了方便查找,将表空间编号和页号作为key与对应的控制模块做一个哈希表,这样就能很方便判断缓存页是否在Buffer Pool。
【5】flush链表
修改了Buffer Pool中缓存页的数据,就需要更新磁盘,每次都更新磁盘就对性能有损耗,所以可以把要更新缓存页收集起来一次性更新,需要更新的缓存页就叫脏页,所有的脏页对应的控制块可以组成一个链表叫flush链表,和free链表类似,都是由基节点和控制块组成,参考上面的free链表图。
【6】LRU链表
当free链表中找不到空闲缓存页时,就需要释放不经常使用的页,所以可以把使用评率高的缓存页放在前面,频率低的缓存页放在后面组成一个LRU链表,这个链表的插入和更新方式如下:
1、如果访问的页不在Buffer Pool中,就从磁盘加载到Buffer Pool中时,把缓存页对应的控制块放到链表的头部,
2、如果访问的页在Buffer Pool中,就直接把缓存页对应的控制块移动链表的头部。
但是有两种情况,第一是InnoDB提供了预读功能,会提前读取认为可能会访问的页到Buffer Pool中,但可能最终都没被访问;另一种情况全表扫描会把所有页都加载到Buffer Pool,但很多页后面可能不会被访问。所以为了解决这两种情况把LRU链表分为两部分:热数据和冷数据,也可以叫young区域和old区域,young区域存储使用频率高的缓存页,old区域存储使用频率不是很高的缓存页,如图:
LRU链表中的young区域和old区域的比例系统变量innodb_old_blocks_pct的值来确定,innodb_old_blocks_pct值代表old区域的百分比比例,如下:
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37 |
+-----------------------+-------+
1 row in set, 1 warning (0.00 sec)
通过young区域和old区域我们就可以对预读和全表扫描进行优化,预读优化如下:
当磁盘上的某个页初次加载到Buffer Pool时,就会放到old区域的头部,如果该页的访问频率低就会在old后移最终被移除。
全表扫描优化如下:
全表扫描一样也是初次会将所有页放到old区域的头部,但是后续读取所有记录代表访问了页多次,就会将页放到young区域,但是全表扫描的数据读取是一次性的,间隔很短,所以需要设置一个时间间隔,此间隔内的访问不会将页移动young区域。
时间间隔配置为innodb_old_blocks_time,单位为毫秒,配置如下:
mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000 |
+------------------------+-------+
1 row in set, 1 warning (0.00 sec)
【7】刷新脏页到磁盘
后台会有专门的线程会将Buffer Pool上修改过页,也就是脏页刷新到磁盘,刷新方式有两种:
第一种是BUF_FLUSH_LRU:
就是定时从LRU链表尾部扫描一些页,发现脏页就更新到磁盘,扫描页的数量可以通过innodb_lru_scan_depth来指定。
查看配置如下:
mysql> SHOW VARIABLES LIKE 'innodb_lru_scan_depth';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| innodb_lru_scan_depth | 1024 |
+-----------------------+-------+
1 row in set, 1 warning (0.00 sec)
第二种刷新方式叫BUF_FLUSH_LIST:
也就是定时从flush链表中刷新一部分页到磁盘。
【8】Buffer Pool实例
可以通过innodb_buffer_pool_instances配置Buffer Pool的实例,提高并发处理速度,配置如下:
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1 |
+------------------------------+-------+
1 row in set, 1 warning (0.00 sec)
每个Buffer Pool实例的大小为:
innodb_buffer_pool_size/innodb_buffer_pool_instances。
【9】chunk
一个Buffer Pool实例可以由多个chunk组成,一个chunk的大小可以如下查看,单位是字节:
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_chunk_size';
+-------------------------------+-----------+
| Variable_name | Value |
+-------------------------------+-----------+
| innodb_buffer_pool_chunk_size | 134217728 |
+-------------------------------+-----------+
1 row in set, 1 warning (0.00 sec)
需要注意的是innodb_buffer_pool_size的大小得是innodb_buffer_pool_instances*innodb_buffer_pool_chunk_size的整数倍。
【10】Buffer Pool状态信息
可以通过show engine innodb statu语句来查看InnoDB运行过程中的一些状态信息,其中就有Buffer Pool的状态,如下:
mysql> show engine innodb status;
...
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 1107296256
Dictionary memory allocated 6746952
Buffer pool size 64832
Free buffers 55706
Database pages 9126
Old database pages 3348
Modified db pages 8971
Percent of dirty pages(LRU & free pages): 13.837
Max dirty pages percent: 90.000
Pending reads 0
Pending writes: LRU 0, flush list 0
Pages made young 14, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 165, created 8961, written 0
0.00 reads/s, 0.00 creates/s, 0.00 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: 9126, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
...
各个字段含义如下:
Buffer Pool状态字段 | 含义 |
---|---|
Total memory allocated | 给Buffer Pool向操作系统申请的连续内存空间大小,由控制块、碎片、缓存页组成 |
Dictionary memory allocated | 给数据字典信息分配的内存空间大小,和Buffer Pool没什么关系 |
Buffer pool size | Buffer Pool可以存储缓存页的数量 |
Free buffers | 空闲缓存页数量,也就是free链表节点数量 |
Database pages | LRU链中节点数量,包含young区域和old区域的节点 |
Old database pages | old区域节点的数量 |
Modified db pages | 脏页数量,也就是flush链表节点数量 |
Pending reads | 正在等待从磁盘上加载到Buffer Pool中的页面数量 |
Pending writes: LRU | 从LRU链表刷新到磁盘中的页数 |
Pending writes: flush list | 从flush链表刷新到磁盘中的页数 |
Page made young | LRU链表中从old区域移动到young区域头部的节点数量 |
Page made not young | 访问old区域在innodb_old_blocks_time时间间隔内访问不能移动到young区域的数量 |
young/s | 每秒从old区域移动到young区域头部节点数量 |
non-young/s | 每秒由于不满足时间限制不能从old区域移动到young区域节点数量 |
Page read | 从磁盘读取写的Buffer Pool中的页数 |
Page created | Buffer Pool中创建的页数 |
Page written | 刷新脏页到磁盘的页数 |
reads/s、creates/s、writes/s | 相关速度 |
Buffer pool hit rate | 某段时间平均访问1000次页面,有多少次页面已经被缓存到了Buffer Pool中 |
young-making rate | 某段时间平均访问1000次页面,有多少次访问使页面移动到了young区域,包含old、young区域移动到young区域 |
not | 某段时间平均访问1000次页面,有多少次访问没有使页面移动到了young区域 |
LRU sum | LRU链表中节点数量 |
I/O sum | 最近50s读取磁盘页的总数 |
I/O cur | 正在读取的磁盘页数量 |
3、总结
Buffer Pool用作缓存,主要用于数据写入、数据读取、数据刷新,目的是为了减少磁盘I/O,增加读写速率,提高数据的访问速度和系统性能。