一、Buffer Pool是什么?
MySQL服务启动时,会向操作系统申请一片连续的内存空间,就叫缓冲池。默认大小为128M,可
以通过innodb_buffer_pool_size 全局变量,增加或减少Buffer Pool占用内存大小。
innodb_buffer_pool_size = 134217728 --单位是字节
为什么要有Buffer Pool?
MySql真实数据是存储在磁盘中的,而读取磁盘速度是非常慢的。为了快速返回所需要的数据,在
InnoDB存储引擎外加一层。字面意思就是加一层缓存,但管理起来是非常麻烦的。
(什么事是没有包一层解决不了的,有的话,那就再包一层)
当访问某条数据时,MySQL会将数据所在的数据页,整体加载到内存中 。后续再此访问时,直接
从缓冲池中读取。创建Buffer Pool的目的 ==> 减少磁盘I/O
二、Buffer Pool管理
1.缓冲页
Buffer Pool中保存的数据页我们叫做缓冲页,大小与数据页一致,均为 16KB。为了更好的管理
缓冲页,InnoDB为每个缓冲页创建一个控制块。控制块中信息主要包括,该页所属的表空间编
号、页号、缓冲页在Buffer Pool中的内存地址指针。控制块与缓冲页是一一对应的。
大致如下图所示
因为控制块、缓冲页的大小是固定的,当剩余空间不足分配控制块、缓冲页时,就会产生碎片。
2.Free链表
在初始化Buffer Pool内存空间,InnoDB会划分若干个控制块和缓冲页。此时还没有发生数据访
问,所以并没有磁盘中的数据页被缓存到Buffer Pool中。
为了后续存储数据页,知道哪一个缓冲页是空闲的,哪一个缓冲页已被占用。这时候就用到了Free
链表。我们把Buffer Pool中所有空闲的缓冲页对应的控制块作为一个节点当到一个链表中,这个链
表就叫做 Free链表。大致如下图所示
Free链表包含,头节点地址、尾节点地址,以及整个链表包含节点的数量 。
注:Free链表头节点占用的内存空间,不在Buffer Pool申请一大片连续的空间内,而是另外单独申
请的内存空间。
后续查询数据时,磁盘中的数据页需要加载到Buffer Pool中时,会从Free链表中取一个空闲的缓冲
页,然后把控制块中所需要的信息补充上,包括页号以及所属表空间编号等,最后把该缓冲页对应
控制块节点,从Free链表中移除。
3.缓冲页查询-哈希表
如何知道查询的数据所在的页在不在Buffer Pool中,以及在Buffer Poo中哪个位置。
Buffer Pool会生成一张哈希表,用所属表空间编号+页号作为key值,用缓冲页对应的控制块中内存
地址指针,作为value值。
在查询时,需要访问某个页的数据时,根据所属表空间编号+页号,是否有对应的value值。如果
有,直接返回缓冲页就可以。如果没有,就从Free链表选一个空闲的缓冲页,将磁盘的数据页加载
到对应的缓冲页中。
4.Flush链表
在修改MySQL数据时,不会立马修改磁盘中的数据,而是修改对应Buffer Pool中缓冲页的数据。
同样的道理,每次修改数据,如果直接修改磁盘中的数据,很慢并且效率低。所有会在某个时间
点,统一修改磁盘中的数据。(具体如何修改,后续文章)
当磁盘中的数据页数据与Buffer Pool中缓冲页的数据不一致时,我们称之为 脏页。
如何知道Buffer Pool中哪些是脏页,哪些是没有被修改的缓冲页。所有Buffer Pool不得不再创建一
个链表,Flush链表 ,将被修改过的缓冲页对应的控制块,作为一个节点加入到Flush链表中。
Flush链表与Free链表大体一致,如下图所示,
注:某个缓冲页对应的控制块,不可能既在Free的节点上,又在Flush节点上。(有点是废话)
5.FRU链表
Buffer Pool对应的内存其实是有限的,当Free链表中已经没有剩余的缓冲页了,就需要从Buffer P
ool中清除某些旧的缓冲页。那如何知道哪些缓冲页需要被删除了,这就用到了FRU链表。
这个链表是按照最近最少使用的原则去淘汰Buffer Pool中的缓冲页,叫做 FRU链表。
FRU链表的原理大致是,客户端请求用到的缓冲页,移动到 FRU链表的头部,这样FRU链表尾部
的缓冲页肯定是不被经常使用的。
预读
InnoDB提供预读的服务,InnoDB认为执行当前的请求时,可能会在后面读取某些数据页,于是就
预先把这些数据页加载到Buffer Pool中。
1.线性预读
如果顺序访问某个区的页面超过系统变量设置的值,就会触发一次异步读取下一个区中全部的页
面,并加载到Buffer Pool中。是异步读取,并不会影响当前工作现场的正常执行。
innodb_read_ahead_threshold默认是56个页。
innodb_read_ahead_threshold = 56
2.随机预读
如果某个区连续13个数据页都被加载到Buffer Pool中,无论这些页面是不是顺序读取的,都会触发
一次异步读取,将整个区的数据页全部加载到Buffer Pool中。通过系统变量设置,默认关闭状态。
innodb_random_read_ahead = OFF
预读本身是一件好事。如果预读中的页,被成功的使用到,可以提高语句的执行效率。但是如果用
不到,就会降低Buffer Pool的命中率。因为Buffer Pool容量不是太大,预读的页会占用容量。并且
预读的页会移动到FRU链表的头部,而链表尾部可能是热点数据,会被移除掉。
全表扫描
当查询条件没有命中索引时,就会全表扫描。同一样的道理,如果查询的数据页非常多,也回触发
FRU链表的移除动作。
影响Buffer Pool命中率
1.加载到Buffer Pool中的数据页不一定被用到。
2.如果有非常多使用频率偏低的缓冲页,并且还在FRU链表的头部,可能会把尾部的热点数据移除
掉。
划分的FRU链表
针对以上情况,InnoDB将FRU链表按照比例分成两部分。一部分是使用频率非常高的缓冲页,也
称为热数据,或者叫做 young区域。另一部分是使用频率不高的缓冲页,也称为冷数据,或者叫做
old区域。
大致如下图,
可以通过全局变量设置young和old两个区域的比例。默认是old占据37%
innodb_old_blocks_pct = 50
有了划分的FRU链表,针对影响Buffer Pool命中率的问题,做了优化。
- 针对预读的数据,全部加载到old区域中,如果后续被读取,会移到young区域。后续没有被读取,会被逐渐移除。
- 针对全表扫描,也是将数据全部加载到old区域中。但如果数据在指定时间内,再次被读取,会移动到young区域。可以通过全局变量 innodb_old_blocks_time设置时间。默认是1000ms
innodb_old_blocks_time = 2000 --单位是ms
- 频繁的移动FRU链表的头部,也会很消耗性能。所以如果访问的缓冲页,在young区域中,并且该缓冲页还在young区域的1/4之后,才会把该页移动到FRU链表young区域的头部。如果在1/4之中,就不会移动。
- .....
6.多个Buffer Pool实例
在多线程环境下,访问Buffer Pool中的各个链表是需要加锁的。遇到访问量特别大时,就会影响
Buffer Pool的响应速度。所以InnoDB可以将Buffer Pool分成若干个小的Buffer Pooll,每个小的
Buffer Pool称为一个实例。它们都是独立的,独立申请空间,独立管理各种链表,在多线程访问下
互不干扰。可以通过innodb_buffer_pool_instances设置实例个数。默认是1
innodb_buffer_pool_instances = 2;
注:当innodb_buffer_pool_size设置小于1GB时,设置innodb_buffer_pool_instances无效。
Chunk
如果在MySQL服务运行之后,修改Buffer Pool的大小。都需要重新向操作系统申请一片连续的内
存空间,然后再将Buffer Pool旧数据,重新复制到新的内存地址。这是非常耗时的。
所以InnoDB不再是一次性申请一大片连续的内存空间,而是以Chunk为单位,申请一片连续的内
存空间,里面包含了若干个控制块以及缓冲页。
如下图所示,
可以通过 innodb_buffer_pool_chunk_size 设置Chunk大小,默认是128M。
innodb_buffer_pool_chunk_size = 134217728 --单位是字节
Buffer Pool配置事项
- innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances 的倍数,保证每个Buffer Pool实例中Chunk的个数是一样的。
- 如果服务启动时,innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances > innodb_buffer_pool_size,服务器会把innodb_buffer_pool_chunk_size 改成 innodb_buffer_pool_size/innodb_buffer_pool_instances
7.查看Buffer Pool的状态
执行 show engine innodb status 命令
show engine innodb status;
--只看Buffer Pool中的内容,其他被删除
=====================================
2024-03-16 10:37:23 0x3d48 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 18 seconds
-----------------
BACKGROUND THREAD
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 0
Dictionary memory allocated 545827
Buffer pool size 8191 --可以存储缓冲页数量 *16KB = 128M
Free buffers 6966 --Free链表还有多少空闲缓冲页
Database pages 1218 --代表FRU链表中young和old的节点数量
Old database pages 469 --FRU链表old区域的节点数
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, --即将从FRU链表刷新到磁盘的缓冲页数量
flush list 0,
single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 1072, created 146, written 331
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 1218, unzip_LRU len: 0
I/O sum[0] --最近50s读取磁盘的页数量
:cur[0] --正在读取磁盘的页数量,
unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=6128, Main thread ID=11880 , state=sleeping
Number of rows inserted 14, updated 0, deleted 0, read 18512
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
Number of system rows inserted 8, updated 342, deleted 8, read 6132
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================