InnoDB的缓存池
作者:鱼仔
博客首页: https://codeease.top
公众号:Java鱼仔
# InnoDB中的页
InnoDB存储引擎基于页的方式存储在磁盘中,它将数据存储在页(Page)的概念上。一个页是 InnoDB 存储引擎中的最小存储单位,它包含了一定数量的数据行、索引节点和其他元数据。
InnoDB 存储引擎中的页包括了数据页和索引页两种类型:
数据页:数据页是存储数据行的页,每个数据页的大小可以通过参数 innodb_page_size 进行配置,默认为 16KB。在数据页中,InnoDB 存储了多个数据行,每个数据行包含了一条记录的所有列数据。
索引页:索引页是存储索引节点的页,每个索引页的大小也可以通过参数 innodb_page_size 进行配置,默认为 16KB。在索引页中,InnoDB 存储了多个索引节点,每个索引节点包含了一个索引键值和一个指向数据页的指针。
但是如果每次查询数据都从磁盘中去查询,就会导致性能很差,于是InnoDB增加了一个缓冲池(Buffer Pool)的东西,用来提高数据库的读写性能。而缓冲池中保存的就是以链表形成的页。
# Buffer Pool 存了哪些东西
Buffer Pool 的大小可以通过参数 innodb_buffer_pool_size 来查看和设置,以我安装的 MySQL 8.0 为例,innodb_buffer_pool_size 默认大小为128M
show variables like 'innodb_buffer_pool_size';
一般生产环境中,如果机器的内存大小是 32G,可以将 innodb_buffer_pool_size 大小设置为16G。
缓冲池并非只有一个,通过 innodb_buffer_pool_instances 参数可以查看和设置缓冲池的个数,默认是一个缓冲池。
show VARIABLES like 'innodb_buffer_pool_instances'
缓冲池中除了缓冲了数据页和索引页,还缓冲了插入缓冲、自适应哈希索引、锁信息、数据字典信息等等,只不过主要还是缓冲数据页和索引页。
Buffer Pool使用控制块控制着各种页,,控制块中存储着对应缓存页,控制块中存储着对应缓存页的所属的表空间、数据页的编号,以及对应缓存页在Buffer Pool中的地址等信息。而控制块的大小约为页的5%,大概是800字节。
# BufferPool如何管理Page页
BufferPool将所有的Page页分为了三种类型:
- free page:空闲page,未被使用
- clean page:被使用page,数据没有被修改过
- dirty page:脏页,page页中数据和磁盘数据发生了不一致
接着Buffer Pool中使用了三种链表来管理这些页:
- free list管理free page,free list把所有空闲的缓冲页对应的控制块作为一个个的节点放到一个链表中。
- flush list管理dirty page,内部page按修改时间排序。Innodb为了提高处理效率,在每次修改缓冲页后,并不是立刻把修改刷新到磁盘上,而是在未来的某个时间点进行刷新操作,所以需要使用flush list 存储dirty page,凡是被修改过的缓冲页对应的控制块都会作为节点加入到flush链表。
- lru list:表示正在使用的缓冲区,管理clean page和dirty page,缓冲区以midpoint为基点,前面链表称为new 列表区,存放经常访问的数据,占63%,后面链表称为old列表区,存放使用较少的数据,占37%。
# Free List
当数据库刚启动时,所有的页都在Free List中,当有页需要存入到Buffer Pool的时候,首先判断Free List中是否有空闲的页,如果有的话就将该页从Free List中剔除,然后放入到LRU List中。
# LRU List
LRU List是Buffer Pool最核心的部分,InnoDB取缓存的数据取得就是 LRU List 的数据页。为了确保尽可能让热点数据在缓存中,LRU List使用了改进版本的LRU算法来实现。
传统的LRU算法会将新加载的数据存放到LRU列表的表头,最近一次访问过的数据也会放到表头,优先淘汰表尾的数据,这样能保证最近经常使用的数据不会被淘汰。
但是在MySQL中如果发生全表扫描或者一些预读机制,很多只会用到一次的数据就进入到了LRU的表头,导致真正的热数据被淘汰,因此MySQL改造了LRU算法。
将LRU拆分为热数据区和冷数据区,每次数据都插入到midpoint开始插入,如果该数据页在LRU链表中超过1s,就进入到热数据区,如果短于1s,则位置不变,1s这个参数由innodb_old_blocks_time控制, 默认为1s。
show VARIABLES like 'innodb_old_blocks_time'
其中热数据区和冷数据区的大小通过 innodb_old_blocks_pct 进行控制,innodb_old_blocks_pct 代表着冷数据区的大小,默认为37%
show VARIABLES like 'innodb_old_blocks_pct'
# Flush List
Flush List 管理着 Buffer Pool 中的 dirty page,Innodb为了提高处理效率,在每次修改缓冲页的数据之后,并不是立刻把修改刷新到磁盘上,而是在未来的某个时间点进行刷新操作,所以需要使用flush list 存储dirty page,凡是被修改过的缓冲页对应的控制块都会作为节点加入到flush链表。 InnoDB 使用 redo log 来避免数据库崩溃但脏页数据还没写入的问题。当执行更新操作时,InnoDB会先把数据写入到 redo log,再写入数据库,保证数据不丢失。
# 脏页如何被刷新到磁盘
缓冲池的大小毕竟是有限的,虽然数据被修改后不会立刻被刷新到磁盘,但是依然需要在满足一定条件的情况下将脏页从缓冲池刷新到磁盘中。在以下几种场景下,脏页会被刷新到磁盘中:
- 数据库正常关闭:当数据库被正常关闭之前,脏页会被刷新到磁盘中。
- redo log 日志满了:MySQL在执行数据变更前会先将变更写入 redo log,保证了数据的持久性,因此在redo log将要满的时候也会把脏页刷新到磁盘中。
- 后台线程刷新:InnoDB有异步的刷新线程和合并线程,这些线程会定期或定时地将脏页刷新到磁盘中。具体来说,InnoDB的刷新线程会根据redo日志和LSN(日志序列号)信息,将脏页刷新到磁盘中;InnoDB的合并线程则会将多个小的脏页合并成一个大的脏页,以减少磁盘IO操作的次数。
- LRU列表淘汰:当LRU列表满了,那些被淘汰的页如果包含脏页,这些脏页就会被刷新到磁盘。
- 主动刷新:可以通过 FLUSH TABLES 命令主动将表的缓存刷入到磁盘中。一般情况下不会主动去刷新缓存。
# Buffer Pool 的运行状态
通过下面这条命令可以看到InnoDB的整体运行状态
show engine innodb status;
通过执行上面的命令可以得到一段很长的日志信息
Per second averages calculated from the last 16 seconds
开头这一行表示这是过去16秒的数据库信息
其中 BUFFER POOL AND MEMORY 下面的表示 BUFFER POOL 的运行状态
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 0
Dictionary memory allocated 448265
Buffer pool size 8192
Free buffers 7137
Database pages 1046
Old database pages 366
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 165, not young 3631
0.00 youngs/s, 0.00 non-youngs/s
Pages read 887, created 160, written 327
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: 1046, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
有几个关键性的数据:
Buffer pool size :表示缓冲池有 8192 个页,占用大小为 8129*16K
Free buffers: Free 列表页的数量
Database pages:LRU 列表页的数量
Modified db pages:Flush list 页大小
其中 Pages made young 165, not young 3631 表示数据从old区域移动到young区域的次数,made young表示移动次数,not young未移动次数。
Buffer pool hit rate:缓冲池的命中率,这个比率100%说明缓冲池运行良好。若该值小于95%说明要看一下是否是全表扫描等操作污染了LRU列表。