Innodb 架构:Buffer Pool
innodb架构
buffer pool 简介
Buffer pool 是mysql的内存结构之一,如果每次读写都要直接磁盘IO,会大大拖慢执行效率,这就是引入buffer pool的原因。buffer pool的结构如下:
缓存页与磁盘页对应,默认16KB。为了管理这些页,引入了控制块,控制块保存了这些页的元信息,主要有:
- 表空间编号
- 页号
- 缓存页在buffer pool的地址
- 链表节点指针
- 锁信息
- LSN信息
与buffer pool一同参与管理的有三个主要的链表:free链表,flush链表,LRU链表。下面分别介绍。
缓存页的哈希处理
如果要访问的数据所在的页已经被加载到buffer pool中,我们就可以直接读写内存。但要如何知道一个页已经被加载到pool中呢?我们通过表空间+页号来标识一个页,因此可以构建一个哈希表:
key:表空间编号+页号
value:控制块
free链表
如果要加载一页到buffer pool中,如何知道哪个缓存页是空闲的呢?这就是free链表提出的背景。mysql刚启动时,所有的缓存页都处于free链表中,每当从磁盘加载一页到pool中时,就从free链表取出一个空闲的缓存页。把该页对应的控制块信息填上(表空间,页号),然后该缓存页对应的节点从free链表移除。
flush链表
如果我们修改了某个页(dirty page),这个页会被加入flush链表,等待刷盘。flush链表的结构和free链表类似。
LRU链表
Buffer pool的空间是有限的,如果需要加载一个新页,但free链表已经用光了,该怎么办?这就是LRU链表提出的背景。当缓存页被写入后,该页从free链表移除,加入LRU链表的头部。但这里有两个情况需要考虑:
-
Innodb 提供预读功能,当innodb认为后续请求可能会访问某些页面,它会提前把这些页面加载到buffer pool,但这些页面后续可能用不到,白白占用了空间;
- 预读分为两种:
-
- 线性预读:如果顺序访问某个区的页面超过某个值(innodb_read_ahead_threshold),innodb就会将下个区的全部页面加载到buffer pool;
- 随机预读:如果buffer pool已经缓存了某个区的13个连续页面,不论这些页面是否是顺序读取的,都会触发一次预读,将本区所有其他页面加载到buffer pool,这个功能通过 innodb_random_read_ahead 控制,默认OFF;
-
全表扫描,会将很多页面放入buffer pool,将buffer pool换血,但这些页面后续很少被访问到。如果这时还有业务数据在读取其他页面,那这些页面就被从buffer pool挤了出去;
上述两个问题都是劣币驱逐良币,为了解决这一点,LRU链表进行了分区。old区的比例由 innodb_old_blocks_pct 控制,默认值37,代表old区占的比例是37%。
规则如下:
- 页加载时会放到old区的头节点
- 访问old区页面的时候,记录第一次访问的时间,如果后续访问时间与第一次访问时间不超过某个阈值(innodb_old_blocks_time )默认1s,那该页面就不会被移动到young区,否则移动到young区头部
第一点对应预读,避免预读加载的页影响young区域活跃的缓存页;
第二点对应全表扫描,扫描时某个页会在短时间内被大量访问,之后就不再访问。如果这种高频访问都在阈值内(在一次全表扫描的过程中,多次访问一个页面中的时间不会超过1s),我们就不移动缓存页;
刷新脏页到磁盘
- 从LRU链表尾部刷脏(
BUF_FLUSH_LRU
),后台线程会定时从LRU链表尾部扫描一些页面,扫描数量通过 innodb_lru_scan_depth 控制,如果这些页里有脏页,会把他们刷新到磁盘; - 从flush链表刷脏(
BUF_FLUSH_LIST
),后台线程会定时从flush链表刷脏;
有时候后台线程刷脏比较慢,导致用户线程加载页面时没有可用的缓存页,这时用户线程就会尝试将LRU链表尾部的脏页同步刷新到磁盘,这被称作BUF_FLUSH_SINGLE_PAGE
,是个很慢的操作。
多个buff pool实例
为了提高并发度,innodb_buffer_pool_instances 管理buff pool实例个数,每个实例是彼此独立的。每个buffer pool占用的空间:
innodb_buffer_pool_size / innodb_buffer_pool_instances
随着多实例的引入,还提出了chunk的概念,每次申请内存以chunk为单位:
innodb_buffer_pool_chunk_size 设置了chunk的大小,默认128M。
innodb_buffer_pool_size 必须是 innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数,这是为了保证每个buffer pool实例包含的chunk数量相同
Buff pool 状态信息
mysql> SHOW ENGINE INNODB STATUS\G
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 13218349056;
Dictionary memory allocated 4014231
Buffer pool size 786432
Free buffers 8174
Database pages 710576
Old database pages 262143
Modified db pages 124941
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 6195930012, not young 78247510485
108.18 youngs/s, 226.15 non-youngs/s
Pages read 2748866728, created 29217873, written 4845680877
160.77 reads/s, 3.80 creates/s, 190.16 writes/s
Buffer pool hit rate 956 / 1000, young-making rate 30 / 1000 not 605 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 710576, unzip_LRU len: 118
I/O sum[134264]:cur[144], unzip sum[16]:cur[0]
- Total memory allocated:buffer pool 总大小;
- buffer pool size:Buffer Pool 可以容纳多少缓存页,注意,单位是页!
- Free buffers:还有多少空闲页,也就是free链表节点数;
- Database pages:LRU链表页数量;
- Old database pages:LRU old区页数量;
- Modified db pages:脏页数量,也就是flush链表节点数;
- Pending reads:等待从磁盘加载的页面数量;
- Pending writes:即将从(LRU,flush链表,单页)刷新到磁盘的页数量;
- Pages read,created,written:读取,创建,写入了多少页。后边跟着读取、创建、写入的速率;
- Buffer pool hit rate:缓存命中率;
- I/O sum:最近50s读取磁盘页的总数;
- I/O cur:现在正在读取的磁盘页数量;
参考
转载自:https://juejin.cn/post/7332381790341300224