程序员潇然 发表于 2022-11-21 20:28:50

InnoDB 核心组件Buffer Pool分析简介(四)

`https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html`

### Buffer Pool

缓冲池(Buffer Pool),InnoDB存储引擎中的一个非常重要的组件

放在内存里的组件这里面会缓存很多的数据,以便于以后在查询的时候,万一你要是内存缓冲池里有数据,就可以不用去查磁盘了

通俗的说,Buffer Pool管理了一块内存区域,用来加速数据的读取和写入,减少磁盘IO。

理论上某种角度来说,如果Buffer Pool无限大,那么MYSQL就成为了纯内存数据库。

!(data/attachment/forum/202211/21/205228enkvvx004pxn2mbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

#### InnoDB Startup Options and System Variables

一些默认值可以通过这个进行查询,不同版本右上角切换下即可

`https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html`

比如:

innodb_buffer_pool_size 默认大小是128MB

下图中,default value为:134217728字节/1024/1024 = 128

同时也说明了最小值和最大值,比如最小值5MB

对于大小的设置,官方文档中有这样一句话:

!(data/attachment/forum/202211/22/172808eine385xm4tons3m.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

换句话说就是如果是专用服务器,建议将**80%的内存设置**为Buffer Pool

!(data/attachment/forum/202211/21/204304z8m9lgj22nvdllgn.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

通过 `SHOW ENGINE INNODB STATUS`查看相关配置,缓冲池指标位于标准监视器输出部分:BUFFER POOL AND MEMORY

可以参见官网详细说明, `(https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html)`

```html
mysql> SHOW ENGINE INNODB STATUS\G
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2018-04-12 15:14:08 0x7f971c063700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 15 srv_active, 0 srv_shutdown, 1122 srv_idle
srv_master_thread log flush and writes: 0
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 24
OS WAIT ARRAY INFO: signal count 24
RW-shared spins 4, rounds 8, OS waits 4
RW-excl spins 2, rounds 60, OS waits 2
RW-sx spins 0, rounds 0, OS waits 0
Spin rounds per wait: 2.00 RW-shared, 30.00 RW-excl, 0.00 RW-sx
------------------------
LATEST FOREIGN KEY ERROR
------------------------
2018-04-12 14:57:24 0x7f97a9c91700 Transaction:
TRANSACTION 7717, ACTIVE 0 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 3
MySQL thread id 8, OS thread handle 140289365317376, query id 14 localhost root update
INSERT INTO child VALUES (NULL, 1), (NULL, 2), (NULL, 3), (NULL, 4), (NULL, 5), (NULL, 6)
Foreign key constraint fails for table `test`.`child`:
,
CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE
CASCADE ON UPDATE CASCADE
Trying to add in child table, in index par_ind tuple:
DATA TUPLE: 2 fields;
0: len 4; hex 80000003; asc   ;;
1: len 4; hex 80000003; asc   ;;

But in parent table `test`.`parent`, in index PRIMARY,
the closest match we can find is record:
PHYSICAL RECORD: n_fields 3; compact format; info bits 0
0: len 4; hex 80000004; asc   ;;
1: len 6; hex 000000001e19; asc       ;;
2: len 7; hex 81000001110137; asc       7;;

------------
TRANSACTIONS
------------
Trx id counter 7748
Purge done for trx's n:o < 7747 undo n:o < 0 state: running but idle
History list length 19
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421764459790000, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 7747, ACTIVE 23 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 9, OS thread handle 140286987249408, query id 51 localhost root updating
DELETE FROM t WHERE i = 1
------- TRX HAS BEEN WAITING 23 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4 page no 4 n bits 72 index GEN_CLUST_INDEX of table `test`.`t`
trx id 7747 lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 6; hex 000000000202; asc       ;;
1: len 6; hex 000000001e41; asc      A;;
2: len 7; hex 820000008b0110; asc      ;;
3: len 4; hex 80000001; asc   ;;

------------------
TABLE LOCK table `test`.`t` trx id 7747 lock mode IX
RECORD LOCKS space id 4 page no 4 n bits 72 index GEN_CLUST_INDEX of table `test`.`t`
trx id 7747 lock_mode X waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 6; hex 000000000202; asc       ;;
1: len 6; hex 000000001e41; asc      A;;
2: len 7; hex 820000008b0110; asc      ;;
3: len 4; hex 80000001; asc   ;;

--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: , aio writes: ,
ibuf aio reads:, log i/o's:, sync i/o's:
Pending flushes (fsync) log: 0; buffer pool: 0
833 OS file reads, 605 OS file writes, 208 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
Hash table size 553253, node heap has 0 buffer(s)
Hash table size 553253, node heap has 1 buffer(s)
Hash table size 553253, node heap has 3 buffer(s)
Hash table size 553253, node heap has 0 buffer(s)
Hash table size 553253, node heap has 0 buffer(s)
Hash table size 553253, node heap has 0 buffer(s)
Hash table size 553253, node heap has 0 buffer(s)
Hash table size 553253, node heap has 0 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number          19643450
Log buffer assigned up to    19643450
Log buffer completed up to   19643450
Log written up to            19643450
Log flushed up to            19643450
Added dirty pages up to      19643450
Pages flushed up to          19643450
Last checkpoint at         19643450
129 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total large memory allocated 2198863872
Dictionary memory allocated 409606
Buffer pool size   131072
Free buffers       130095
Database pages   973
Old database pages 0
Modified db pages0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 810, created 163, written 404
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: 973, unzip_LRU len: 0
I/O sum:cur, unzip sum:cur
----------------------
INDIVIDUAL BUFFER POOL INFO
----------------------
---BUFFER POOL 0
Buffer pool size   65536
Free buffers       65043
Database pages   491
Old database pages 0
Modified db pages0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 411, created 80, written 210
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: 491, unzip_LRU len: 0
I/O sum:cur, unzip sum:cur
---BUFFER POOL 1
Buffer pool size   65536
Free buffers       65052
Database pages   482
Old database pages 0
Modified db pages0
Pending reads      0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 399, created 83, written 194
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: 482, unzip_LRU len: 0
I/O sum:cur, unzip sum:cur
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Process ID=5772, Main thread ID=140286437054208 , state=sleeping
Number of rows inserted 57, updated 354, deleted 4, read 4421
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
```

### 数据页面与Buffer Pool结构

官方文档地址:`https://dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool.html`

MySQL对数据抽象出来了一个数据页的概念。

把很多行数据放在了一个数据页里,也就是磁盘文件中就是会有很多的数据页,每一页数据里放了很多行数据

所以一行行的数据是以数据页的形式保存在内存中的

!(data/attachment/forum/202211/21/205802pd98998866b3889x.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

而Buffer Pool中默认情况下,一个缓存页的大小和磁盘上的一个数据页的大小是一一对应起来的,都是16KB。

### 数据页管理

所以数据页进入内存中的Buffer Pool之后,就映射为 `缓存页`

!(data/attachment/forum/202211/22/110309px1x66th3yqss2an.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

在Buffer Pool中,每个缓存页的描述数据放在最前面,然后各个缓存页放在后面

!(data/attachment/forum/202211/22/111456p2dqiirzppirf2b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

Buffer Pool中的描述数据大概相当于缓存页大小的5%左右,假定一个数据页(缓存页)16KB,那么5%就是16K * 5%=819.2字节

假设你设置的buffer pool大小是128MB,加上描述信息128MB+128*5%MB = 134.4MB,所以实际可能有个130多MB

### 缓存页管理

假定缓存页就是16KB,初始的时候,Buffer Pool缓冲池就都是空闲的,可以认为一格一格的内存空间,每个格子16KB(具体格式往下看,此处粗略图)。

!(data/attachment/forum/202211/22/110514xnv6n8r8n68brrwt.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

所以Buffer Pool需要有两个大方面的管理能力:

!(data/attachment/forum/202211/22/110922kcrfcbjqtonvtdjr.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

数据库只要一启动,就会按照你设置的Buffer Pool大小,稍微再加大一点,去找操作系统申请一块内存区域,作为Buffer Pool的内存区域。

然后当内存区域申请完毕之后,数据库就会按照默认的缓存页的16KB的大小以及对应的800个字节左右的描述数据的大小,在Buffer Pool中划分出来一个一个的缓存页和一个一个的他们对应的描述数据。

也就是上面图中这一块

!(data/attachment/forum/202211/22/112655kwp4wwp11aq4xnzy.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

**这个可以认为就是Buffer Pool的内存结构(类比JVM运行时区域),没有数据的时候也是这样的格式,加载数据页后,描述块被写入数据,缓存页也被写入数据。**

对于加载进来的数据页,在加载数据页的时候,通过一些附加的描述信息,可以对数据页进行管理。

对于Buffer Pool的缓存页呢?当数据页需要加载的时候,加载到哪个缓存页呢?

答案是通过空闲链表,free链表

是一个双向链表数据结构,这个free链表里,每个节点就是一个空闲的缓存页的描述数据块的地址

也就是说,**只要一个缓存页是空闲的,那么他的描述数据块就会被放入这个free链表中**

!(data/attachment/forum/202211/22/114134sy556ioux6voincv.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

左下角的这个空闲链表,当初始时,是满的;当缓存快满了的时候,空闲链表也是快要用尽的。

**注意:**

上图中的 `描述`,与buffer pool中的是同一个,只是通过不同的指针相连,同一个描述块,他有指针指向缓存页,也有指针指向各自节点,即使我们自己来实现也不会复制一份。下图这样,更准确

!(data/attachment/forum/202211/22/115120bdkq1en80d0333e7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

`描述`两个指针,一个是free_pre,一个是free_next,分别指向自己的上一个free链表的节点,以及下一个free链表的节点

### 脏页管理

如果发现数据页没缓存,那么必然会基于free链表找到一个空闲的缓存页,然后读取到缓存页里去

但是如果已经缓存了,那么下一次就必然会直接使用缓存页。

缓存页是从磁盘读取的,对于增删改,缓存页会发生变化,那么就出现了缓存页与数据页不一致的情况,这种不一致需要最终落实到磁盘文件系统中。

而这个与磁盘文件不一致的缓存页,就被叫做脏页。

对于脏页,需要刷新到磁盘,但是并不是所有的缓存页都需要刷新,因为有些缓存页还没有发生变化,是因为查询被加载到缓存页中的。

所以,哪些缓存页是脏页呢?

答案是一个跟free链表类似的flush链表

flush链表本质也是通过缓存页的描述数据块中的两个指针,让被修改过的缓存页的描述数据块,组成一个双向链表

!(data/attachment/forum/202211/22/170653sehpmma96os6hmlz.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

针对于描述块,通过free链表以及flush链表两个链表,就可以完成缓存页的分配以及脏页记录。

基础节点与平常的双向链表略有不同,还记录了节点的个数,然后就是通过pre以及next指针,完成链表的连接。

对于free链表,初始时是满的,随着使用变少;

对于flush链表,初始时是空的,随着使用变多,随着刷盘在变少;

### Buffer Pool满了怎么办?

数据页加载到Buffer Pool 中的缓存页中,不管是增删改查,都需要加载,那么总有满的时候,满了怎么办?

就必须要淘汰掉一些缓存页,淘汰谁?

对于缓存页,与热点数据逻辑一样,我们需要淘汰那些用的不频繁的,也就是最近很少使用到的。

常用的算法就是LRU,Least recently used,最近最少使用

算法的核心是维护一个链表,主要步骤如下:

1、新数据插入到链表头部;

2、每当缓存命中(即缓存数据被访问或者修改),将数据移动到链表头部;

3、当链表满的时候将链表尾部的数据丢弃;

!(data/attachment/forum/202211/22/175008q4qf6qmfqzqomif6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

与free链表、flush链表类似,LRU链表也是一个双向链表,flush链表,每当有数据页新加入时就会节点加入链表

每当新数据页加载,插入头部;

缓存页查询、变动后,插入头部;

思路很简单,当满时,从链尾将缓存页删除或者刷入磁盘即可。

### 简单LRU问题

上图是一种最通俗常用的形式

假设有这样一种场景:

总共有10个缓存页,9个缓存页一直在反反复复的被使用,最后一个缓存页最近只用过一次。

在缓存页用完的时候,刚好最后一个缓存页被使用,于是他插入了链表头部,原本使用非常频繁的一个,却在队尾,被淘汰掉了。

这合理吗?

#### MYSQL预读机制

当从磁盘加载数据页到缓存页中时,并不是仅仅只加在一页,他会把连续的几页都加载到Buffer Pool中,而且,还会被插入到头部。

预读机制的理论依据是局部性原理中的空间局部性-与被访问地址相邻的地址上信息可能会在短期内被再次访问。

但是如果此时缓存页耗尽,却又加重了上面提到的问题,最常使用的被淘汰,因为预读机制加载进来的,都还没有被任何人使用过的缓存页,反而存活下来,这也是不合理的。

#### 全表扫描

当没有索引或者条件不具备的时候,会进行全表扫描,这个表里所有的数据页,都从磁盘加载到Buffer Pool里去,如果表比较大,超出了Buffer Pool的大小,这一次扫描之后,Buffer Pool中全都是这个表的数据,而这些数据,后面可能再也不用了。

所以传统的LRU算法是存在不少问题的。

### 优化的LRU算法

直接上官方图,看不懂的继续往下看分析

`https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html`

!(data/attachment/forum/202211/22/193827sus4du7554uoo2os.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

问题的归根结底就是有一部分非经常访问的数据,在经过一次访问之后,插入到了链表的头部,影响了真正的高频数据。

解决思路是冷热分离,一个链表,拆分成串联到一起的两个链表,一个冷链,一个热链。

这个冷就是对应图中的Old,热对应上图中的New(也叫做Young)

数据最开始初始加载,是加载到Old的头部,数据随着使用频率的增加会变得越来越年轻,也就是越来越往头部移动。

上图中右侧两个箭头

向上箭头,表示访问越多越往上(越年轻);向下箭头,表示未使用越往下(越老);

!(data/attachment/forum/202211/22/195442c9558vllvg331qy3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

1. 空间按照比例划分,New(热数据) 5/8,Old(冷数据) 3/8;
2. 数据页第一次被加载到缓存,放在Old(冷数据)区域的头指针处,也就是分界点往后;
3. 如果innodb_old_blocks_time 时间之后,比如1s,数据被访问,会被移动到New区域的 头部;
4. 各个区域内按照访问顺序移动到本区域的头部,New 就是New, Old就是Old,不跨区;(注意说明)
5. 如果空间满了,从Old(冷数据)区域链表尾部进行淘汰。

##### 关于第4条的补充说明

实际上为了性能考虑,不频繁的改动链表。

如果LRU前面1/4位置的数据被访问,不移动;

后面3/4位置的数据被访问,会移动到链表头部;

#### 划分比例

> 官网有默认配置说明:default value is 37 (that is, 3/8 of the pool).

`https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_old_blocks_pct`

!(data/attachment/forum/202211/22/193619c8pfuyn3cmmdrsty.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

#### innodb_old_blocks_time 参数

`https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_old_blocks_time`

!(data/attachment/forum/202211/22/200927yi2rsuxr2s27eswe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

#### 之前的问题

通过分成New/Old两各部分,冷热数据分离,就不会存在一个数据很偶尔的被访问一次,反而跑到链表头部,导致高频数据被淘汰,因为这种数据只会在冷链(old)中;

对于预读机制加载的数据,如果没有后续访问,他也是一直在冷链(old)中;

对于全表扫描这种,没有后续访问,通常也都是在innodb_old_blocks_time 内访问完成,也一直在冷链(old)中;

所以两部分,冷热分离,性能得到了很大的优化。

### 数据写入时机

#### 空间不足

free链表空间耗尽,会进行缓存页淘汰。

#### 定时淘汰

不仅仅是缓存页用光的时候,有一个后台线程,会定时的将位于冷链(Old)尾部的数据,写入磁盘;

这样就形成了一个循环,不断地有缓存页被写入,就不断的有空闲缓存页,随着系统运行,不断地有缓存页被加载进来;

#### 定时flush

除了需要定时淘汰冷链末尾的数据,还需要对脏页数据进行磁盘写入,这个过程也是定时的。

flush链表中缓存页刷入磁盘后,那么这些缓存页也会从flush链表和lru链表中移除,然后加入到free链表。

### Buffer Pool的切分

Buffer Pool划分为缓存页,缓存页的描述块信息,涉及到free 链表,flush链表,LRU链表,那么必然涉及并发问题。

当客户端多个请求时,系统 并发处理,对这些链表的操作需要加锁控制。

否则多线程并发操作,必然有线程安全问题。

一旦进行加锁,部分代码就会串行处理,不过因为是在内存中,即使是串行,数据库性能也不会有太大问题。

但是是不是还可以继续优化一点?

设计者针对这个场景,给出的答案就是拆分Buffer Pool,将原先的一个,拆分为多个。

通过多个Buffer Pool减少并发压力,通过参数 `innodb_buffer_pool_instances` 进行设置

官方文档如下:

`https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances`

> The number of regions that the InnoDB buffer pool is divided into. For systems with buffer pools in the multi-gigabyte range, dividing the buffer pool into separate instances can improve concurrency, by reducing contention as different threads read and write to cached pages. Each page that is stored in or read from the buffer pool is assigned to one of the buffer pool instances randomly, using a hashing function. Each buffer pool manages its own free lists, flush lists, LRUs, and all other data structures connected to a buffer pool, and is protected by its own buffer pool mutex.
>
> This option only takes effect when setting innodb_buffer_pool_size to 1GB or more. The total buffer pool size is divided among all the buffer pools. For best efficiency, specify a combination of innodb_buffer_pool_instances and innodb_buffer_pool_size so that each buffer pool instance is at least 1GB.
>
> The default value on 32-bit Windows systems depends on the value of innodb_buffer_pool_size, as described below:
>
> If innodb_buffer_pool_size is greater than 1.3GB, the default for innodb_buffer_pool_instances is innodb_buffer_pool_size/128MB, with individual memory allocation requests for each chunk. 1.3GB was chosen as the boundary at which there is significant risk for 32-bit Windows to be unable to allocate the contiguous address space needed for a single buffer pool.
>
> Otherwise, the default is 1.
>
> On all other platforms, the default value is 8 when innodb_buffer_pool_size is greater than or equal to 1GB. Otherwise, the default is 1.

除了32位Windows,在所有其他平台上,当innodb_buffer_pool_size大于或等于 1GB 时,默认值为 8。否则,默认值为 1。

如果小于1GB,这个参数配置无效;

最好把 `innodb_buffer_pool_instances` 和 `innodb_buffer_pool_size` 两个参数综合考虑一起设置,保障每块至少1GB;

### chunk机制与动态调整

MYSQL提供了chunk机制,借助于chunk机制,内存划分的更加细小,对于内存空间的操作更加灵活方便。

!(data/attachment/forum/202211/23/113006j2df2h2dn0bfrrdm.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

所以对于Buffer Pool 来说,本身可以划分为多个Buffer Pool块,每块内部又由多个chunk组成,chunk是一块连续的内存空间,里面包含了若干缓冲页与其对应的描述控制块。

#### 整体结构

!(data/attachment/forum/202211/23/175810jwxlaul9at8t9ull.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

**Buffer Pool总大小:**

innodb_buffer_pool_size

约束整个空间的大小

**Buffer Pool 实例数:**

innodb_buffer_pool_instances

设置分块的个数,空间总大小 / 实例个数= 单个分区的大小
应该综合设置这两个参数,单个大小>=1GB

**单个chunk大小:**

innodb_buffer_pool_chunk_size

Buffer Pool 分块内部细分连续空间的大小,
(空间总大小 / 实例个数= 单个分区的大小) / 单个chunk大小 = 单个Buffer Pool分块内chunk个数

默认是128MB

!(data/attachment/forum/202211/23/180445gwqw0myuu0w00es8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

### 动态扩展

如果没有chunk机制,当想要扩容时,怎么办?

一种可能的方式是新开辟一块新的内存空间,将原有Buffer Pool拷贝到新的内存空间中去,但是Buffer Pool往往都是很大的,这显然会比较耗时。

有了chunk机制,这种更小的内存划分单元,动态扩展就变得相对容易了。

比如:

Buffer Pool 大小8GB,实例个数4,那么每一块Buffer Pool将会有2GB空间,如果chunk默认128GB,那么每一块将会有16个chunk。

当想要调整空间时,只需要对每个实例增加chunk的个数即可。

比如扩展到16GB,那么增加8GB,每个chunk默认128MB,总共会多出来,64个chunk,总共4个实例,针对每个实例,新增16个chunk即可。

Buffer Pool 官方是建议专用机器,分配80%的内存。

因为如果Buffer Pool比较小的话,面临着频繁的缓存页换入换出,磁盘写入,性能将会明显下降,简单说就是条件允许的情况下,内存越大越好。

另外关于参数调整,官方有明确的提示信息:

> 更改[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size)值时,以下条件适用:
>
> * 如果初始化缓冲池时[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size) *`innodb_buffer_pool_instances`大于当前缓冲池大小,则[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size)被截断为[`innodb_buffer_pool_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size) /[`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances)。
> * 缓冲池大小必须始终等于[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size) *[`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances) 或倍数。如果更改[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size),[`innodb_buffer_pool_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size)将自动舍入为等于[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size) *[`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances) 的值或倍数。初始化缓冲池时将进行调整。
>
> 为避免潜在的性能问题,块数 ([`innodb_buffer_pool_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size) / [`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size)) 不应超过 1000。
>
> [`innodb_buffer_pool_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size)变量是动态的,它允许在服务器联机时调整缓冲池的大小。但是,缓冲池大小必须等于 [`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size) * [`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances) 的倍数,并且更改这些变量设置中的任何一个都需要重新启动服务器。

关键信息解读:

**前提:**

实例个数必然是整数,每个Buffer Pool实例中chunk的个数也必然是整数。

所以 假设单个Buffer Pool中chunk 的个数为N,那么有等式:

(N **[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size))** [`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances)   = `innodb_buffer_pool_size`

很显然,缓冲池大小必须始终等于[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size) *[`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances) 或倍数,因为N是整数。

**首先**

如果[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size) *`innodb_buffer_pool_instances`大于当前缓冲池大小,也就是说即使N=1,也不足以使等式成立,那么就会对你设置的值进行截断处理,让他至少是1倍。

**其次**

如果因为设置 `innodb_buffer_pool_chunk_size` 的数值,导致这个公式成立时,N不是整数,那么系统将会自动调整。

**最后**

[`innodb_buffer_pool_chunk_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_chunk_size)[`innodb_buffer_pool_instances`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_instances) , chunk大小,以及Buffer Pool实例个数是无法动态调整的,如果需要更改,要重启。

Buffer Pool总大小,是可以动态调整的,不需要重启,他是通过增加每块Buffer Pool中chunk的个数来实现变化的。

> `innodb_buffer_pool_size` can be set dynamically, which allows you to resize the buffer pool without restarting the server.
>
> The `Innodb_buffer_pool_resize_status` status variable reports the status of online buffer pool resizing operations.

吐槽一下,这几个参数的设置,真的很别扭。

> When [`innodb_dedicated_server`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_dedicated_server) is enabled, `InnoDB` automatically configures the following variables:
>
> * [`innodb_buffer_pool_size`](https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size)

如果配置了参数 `innodb_dedicated_server` ,将会自动配置 ``

`https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_buffer_pool_size`


!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-229-1-1.html `
页: [1]
查看完整版本: InnoDB 核心组件Buffer Pool分析简介(四)