Files
rikako-note/mysql/mysql文档/innodb体系结构.md
2024-12-19 12:58:47 +08:00

507 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# innodb体系结构
## innodb体系结构
innodb体系结构由如下部分构成
- innodb存储引擎内存池
- 后台线程
- 磁盘上的文件数据
innodb内存池由多个内存块构成内存快负责如下功能
- 维护进程、线程访问的内部数据结构
- 对磁盘上的文件数据进行缓存(加快读取速度),同时缓存对磁盘数据的修改
- redo log缓冲
在innodb存储引擎中后台线程主要负责刷新内存池中的数据保证内存缓存为最新状态。此时后台线程还负责将内存池中的修改刷新到磁盘中。
## mysql存储结构
mysql存储结构如图所示
![mysql storage structure](https://www.mysqltutorial.org/wp-content/uploads/2024/01/mysq-innodb-architecture.png, "mysql存储结构")
mysql存储结构主要分为两部分
- 内存结构
- 磁盘存储结构
### 内存结构
mysql内存结构主要是为了管理和优化数据存储和数据检索mysql内存结构包含
- 缓冲区Buffer Pool
- Change Buffer
- 自适应哈希索引Adaptive hash index
- Log Buffer
#### Buffer Pool
buffer pool主要用于缓存经常访问的数据
#### change Buffer
change buffer主要用于缓存针对辅助索引页面的修改。当被修改的辅助索引页面不存在于内存中时将修改缓存在change buffer中当后续辅助索引页面被加载到内存中时会将对该页面的修改进行合并。
#### Adaptive hash index
自适应哈希索引是一个内存结构,用于优化部分读取操作的性能。其提供了一个快速内存检索机制,用于加速对索引页的频繁访问。
#### log Buffer
log buffer是一个内存区域用于存储将要被写入到transactiion log中的数据。
### 磁盘存储结构
innodb存储引擎将数据持久化存储到磁盘中。磁盘存储的结构如下所示
- system tablespace
- file-per-table tablespaces
- general tablespaces
- undo tablespaces
- temporary tablespaces
- double write buffer
- redo log
- undo log
#### system tablespace
system tablespace作为change buffer的存储区域。
innodb用一个或多个文件来存储system tablespace默认情况下mysql会创建名为`ibdata1`的文件。
`innodb_data_file_path`决定了system tablespace files的大小和数量。
## 后台线程
innodb采用多线程模型存在多个后台线程每种后台线程负责不同的后台任务。
### Master Thread
Master Thread主要负责将内存池中的缓存数据异步刷新到磁盘中包括脏页的刷新、合并`插入缓冲`insert bufferundo页的回收等。
### IO Thread
innodb中使用AIO来处理IO请求IO Thread主要则用来处理异步IO的回调其中`innodb_read_io_threads``innodb_write_io_threads`默认均为4个。
```sql
show variables like 'innodb_%_io_threads'
```
|Variable_name|Value|
|-------------|-----|
|innodb_read_io_threads|4|
|innodb_write_io_threads|4|
### Purge Thread
当事务被提交之后其对应的undo log不再会被需要需要Purge Thread来回收已经使用的undo页。
可以设置多个purge threads默认情况下mysql 8的purge threads为4个
```sql
show variables like 'innodb_purge_threads'
```
|Variable_name|Value|
|-------------|-----|
|innodb_purge_threads|4|
### Page Cleaner Thread
page cleaner thread将脏页刷新的任务放到单线程中来完成从而减轻原Master Thread的工作以及减少对用户查询线程的阻塞。
## 内存
### 缓冲池
innodb存储引擎是基于磁盘存储的并将记录基于页的方式进行管理。为了提升数据库系统的读写性能通常采用缓冲池来提升数据库的整体性能。
> ### 缓冲池原理
> #### 读缓冲
> 在数据库读取磁盘上的页面时,会将丛磁盘上读取到的页存放到缓冲池中,后续再读取相同的页数据时,先丛缓冲池中查找。如果缓冲池中存在该页,直接从缓冲池中读取。
> #### 写缓冲
> 在数据库针对磁盘上的页数据进行修改时,首先会尝试修改缓冲池中的页数据,并且,缓冲池中的页数据会定期刷新到磁盘中。
>
> 缓冲池刷新页到磁盘中的操作由checkpoint机制进行触发并不会在每次更新缓冲池中的页数据后立马触发。
### 缓冲池参数配置
对于innodb其缓冲池大小通过`innodb_buffer_pool_size`来配置。默认情况下,`innodb_buffer_pool_size`大小为128M.
```sql
show variables like 'innodb_buffer_pool_size'
```
|Variable_name|Value|
|-------------|-----|
|innodb_buffer_pool_size|134217728|
缓冲池中缓存的数据页类型如下:
- 索引页
- 数据页
- 插入缓冲insert buffer
- 自适应哈希索引
- innodb存储的锁信息
- 数据字典信息
在mysql 8中innodb可以支持多个缓冲池实例每个页根据hash值不同被散列到不同缓冲池实例中这样可以提高应用的并发能力。
缓冲池实例数量可以通过`innodb_buffer_pool_instances`变量来进行设置该变量默认值为1.
#### innodb_buffer_pool_size
当修改`innodb_buffer_pool_size`操作将会在chunk上执行。chunk size通过`innodb_buffer_pool_chunk_size`来配置。
`M = innodb_buffer_pool_chunk size * innodb_buffer_pool_instances`
`innodb_buffer_pool_size`必须等于`M`或是`M`的整数倍。如果`innodb_buffer_pool_size`不等于M且不是M的整数倍那么`innodb_buffer_pool_size`将会被自动调整到等于M或是M的整数倍。
> #### 设置innodb_buffer_pool_size示例
> `innodb_buffer_pool_chunk_size`其默认大小为128M如果将`innodb_buffer_pool_instances`调整为16那么`M`值为`128M * 16 = 2G`.
>
> ##### 将innodb_buffer_pool_size设置为8G
> 由于`8G = 2G * 4`那么8G是2G的整数倍此时该innodb_buffer_pool_size有效
>
> ##### 将innodb_buffer_pool_size设置为9G
> 由于9G不是2G的整数倍那么innodb_buffer_pool_size将会被自动调整到10G10G是2G的整数倍
### LRU List, Free List, Flush List
缓冲池是一块由页构成的内存区域。
innodb中缓冲池通过LRU算法来进行管理LRU中最频繁使用的页放在最前端而较少使用的页放在最尾端。当缓冲池中内存已满不能存放新读取到的页时会释放LRU尾端较少使用的页。
innodb中`页大小默认为16KB`.
#### midpoint
在LRU中新读取的页`并不放在LRU的首部而是放在midpoint的位置该算法被称为midpoint insertion strategy`
默认情况下midpoint位于`5/8`的位置离首部5/8 离尾部3/8。在innodb中`首部 -> midpoint`部分的页称之为new列表`midpoint -> 尾部`部分称之为old列表。
midpoint位置可以通过`innodb_old_blocks_pct`来进行控制,默认情况下该值为`37`
```sql
show variables like 'innodb_old_blocks_pct'
```
结果为
|Variable_name|Value|
|-------------|-----|
|innodb_old_blocks_pct|37|
引入midpoint的原因是防止在进行数据扫描等操作时热点数据被淘汰。
此外innodb还引入了`innodb_old_blocks_time`变量来管理LRU列表代表LRU列表在被读取到midpoint位置后需要经过多久时间才能被加入到`LRU的new部分`
默认情况下,`innodb_old_blocks_time`该值为1000单位为ms如果增加该值会使新页面更快的从缓冲区中淘汰。
#### page made young
在innodb LRU中使用了midpoint insertion的方法来对LRU列表进行管理。当需要将一个新的页添加到缓冲池时最近最少被使用的页将会从缓冲池中淘汰并且新的页将会被插入到midpoint位置。
midpoint将LRU列表分为了两部分
- young部分该部分为head到midpoint的部分默认占列表长度的5/8用于存放访问频繁的页
- old部分用于存放访问频率较少的页
默认情况下LRU通过如下算法管理缓冲池
- 3/8部分属于old sublist部分
- 当innodb读取页到缓冲池中时新读取的页将会被插入到midpoint位置。页可能因为如下原因被读取
- 由用户发起的操作例如sql查询
- 由innodb执行的read ahead操作
- 当访问old部分的页时`made young`即将页从old部分移动到young部分的头部。
- `如果old部分的页是因为用户发起的查询被读取到缓冲池那么该页马上会被访问并且该页会被made young`
- `如果该页是被因为innodb的read ahead被读取到缓冲池中那么该页可能不会马上被访问甚至知道该页被淘汰也不会被访问`
- 随着数据库的运行LRU中的young部分和old部分页面都会向LRU列表尾部移动这被称为`老化`。当其他页面触发`made young`操作时:
- 如果发生`made young`操作那么young部分和old部分的节点都会向后移动发生老化
- 如果由新页面被插入到midpoint那么只有old部分的节点会发生老化
> #### table scan导致的问题
> 默认情况下因用户发起的查询操作而被读取的页面挥别马上移动到LRU young部分。例如mysqldump操作或不带where条件的select都会发起table scan而table scan会读取大量数据到缓冲区中此时会对缓冲区中旧的页进行淘汰。通常table scan读取到缓冲区的新数据后续可能并不会被用到
>
> table scan会快速的将原本处于young部分的页面向后推动到old部分通常这些页都是被频繁使用的。
如上所述table scan会将大量页读取到缓冲池中但是这些页只会在短期内被很快的访问过几次访问会导致该页被made young移动到young部分只会就不会再用到这样会导致大量原本被访问的页被淘汰。
为了解决该问题innodb引入了`innodb_old_blocks_time`在第一次访问了位于old区域的页后的`innodb_old_blocks_time`时间范围内再次访问该页并不会导致该页被移动到LRU的young部分。`innodb_old_blocks_time`的默认值为1000增加该值将会使old部分的页触发`made young`的条件变得更苛刻old部分老化和淘汰的速度也会更快。
#### youngs/s & non-youngs/s
- youngs/s: `youngs/s`该指标代表每秒平均的`因访问old页从而导致made young的访问次数`,如果相同的页发生多次访问,那么所有的访问将都会被计入
- non-youngs/s: 该指标代表`访问old页且没有导致made young的访问次数`,如果相同页发生多次访问,那么所有的访问都会被计入
如果在没有大量扫描发生的情况下youngs/s的指标值仍然很小那么可以考虑适当降低`innodb_old_blocks_time`的值让更多的页更快进入young部分。同样可以适当增加old部分的百分比从而可以令old页更慢移动到LRU尾端更有可能被made young。
如果在发生大量扫描的情况下non-youngs/s的指标值仍然不高那么可以考虑增加`innodb_old_blocks_time`的值延长old页不触发made young的时间窗口
#### buffer pool hit rate
buffer pool hit rate代表缓冲池中页的命中率如果命中率高则代表该缓冲池运行良好。如果命中率较低则需要考虑是否应增加`innodb_old_blocks_time`的值避免table scan导致缓冲池被污染。
#### FreeList
当数据库实例刚启动时LRU里列表中并没有任何页此时页都存放在Free List中。当要从缓冲池中获取页时首先查看Free List中是否有空闲的页`如果有则从FreeList中获取并将该页添加到LRU的midpoint位置``若Free List中没有空闲的页那么将根据LRU算法淘汰LRU尾部的页将淘汰页的内存空间分配给新的页。`
#### Flush List
LRU中被修改的页称其为`脏页`在缓冲池中的数据被修改之后并不会马上就刷新到磁盘中而是会通过checkpoint将脏页刷回到磁盘中。
FlushList即是脏页的列表需要注意的是脏页既存在于LRU中又存在于FlushList中LRU和FlushList都管理的是指向该内存页的指针LRU管理缓冲而FlushList管理脏页回刷二者互不影响。
### redo log buffer
innodb存储引擎的内存区域中除了有缓冲池之外还存在redo log buffer。innodb首先会将redo log放入到这个缓冲区然后会按照一定频率将其刷新到redo log文件。
redo log缓冲区大小并不需要很大通常每隔1s会将redo log buffer中的内容刷新到文件中。`innodb_log_buffer_size`负责控制该缓冲区域大小,该参数默认值为`8M`。(mysql 8中实际测试为`16M`)
redo log buffer在如下场景下会被刷新到文件中
- master thread每秒将缓冲刷新到磁盘文件中
- 每个事务提交时都会将redo log buffer刷新到磁盘文件中
- 当redo log buffer剩余空间小于一般时redo log buffer刷新到磁盘文件中
## checkpoint
在innodb中对数据页的修改都是在缓冲池中完成的在对内存页进行修改后内存页和磁盘上的页内容不一致此时被称为`脏页`
在存在脏页时如果在脏页被刷新到磁盘时数据库发生宕机那么将会发生数据修改的丢失。为了解决该问题innodb采用了`write ahead log`策略即当事务提交时先写redo log再修改页。当发生数据丢失时可以通过redo log来完成数据的修复。
checkpoint解决了如下问题
- 缩短数据库恢复时间
- 缓冲池不够用时,将脏页刷新到磁盘
- redo log不可用时刷新脏页
### 缩短数据库恢复时间
通过checkpoint数据库并不需要在宕机重启之后对所有日志执行redo操作checkpoint之前的页都已经刷新到磁盘。故而数据库只需要对checkpoint之后的数据执行redo操作即可。
### 缓冲池不够用
当缓冲池不够用时LRU会进行页面淘汰此时被淘汰的页如果是脏页需要强制执行checkpoint将脏页刷新到磁盘中。
### redo log不可用
redo log类似循环队列checkpoint之前的位置都已经被刷新到磁盘中可以被覆盖使用。如果当redo log文件中所有的内容都未被刷新到磁盘中那么此时会强制触发checkpoint。
### LSN
checkpoint代表的是`最后被写入到磁盘文件中的变更`通过LSN来进行表示。
LSN为Log Sequence Number该值不断递增代表了和`redo log`中操作记录相关的时间点。LSN代表的时间点和事务的开始和结束时间并不相关LSN可以处于一个或多个事务的中间
LSN在innodb内部被使用用于`crash recovery`和管理缓冲池。LSN长度为8字节
> 位于`checkpoint`之前的变更都已经被写入到磁盘中
### Fuzzy Checkpoint
innodb中实现了fuzzy checkpoint机制会基于小批量small batches来将buffer pool中的页刷新到磁盘中。`并不需要在一次batch中将buffer pool中的页都刷新到磁盘中否则checkpoint过程会中断用户的sql语句处理`
`crash recovery`的过程中innodb会查找已经写入到log file中的checkpoint位于checkpoint之前的内容已经被全部写入到数据库的磁盘文件中innodb会扫描checkpoint之后的内容并且将log file中的修改都应用到数据库中。
> 如果数据库事务发生宕机且缓冲池中存在部分变更尚未被写入到磁盘那么数据库实例重启之后会查看redo log日志中位于`checkpoint`之后的内容,并且将`checkpoint`之后的变更写入到磁盘中。
### checkpoint种类
innodb内部使用Fuzzy Checkpoint进行页的刷新只会将一部分的脏页刷新到磁盘。
innodb中存在如下集中类型的fuzzy checkpoint
- Master Thread Checkpoint
- FLUSH_LRU_LIST_CHECKPOINT
- Async/Sync Checkpoint
- Dirty Page Too Much Checkpoint
#### Master Thread Checkpoint
在Master Thread大约以固定的间隔将一定比例的脏页刷新到磁盘中。Master Thread刷新脏页的操作是异步的用户线程并不会因之阻塞。
#### FLUSH_LRU_LIST_CHECKPOINT
在innodb中需要保证LRU中有一定数量的空闲页如果空闲页少于该数量那么innodb会将位于LRU尾端的页面淘汰。如果被淘汰页中存在脏页那么对这些脏页需要执行checkpoint操作。
`flush_lru_list_checkpoint`操作在单独的page cleaner线程中被执行可以通过`innodb_lru_scan_depth`来控制lru中可用页的数量该值默认为`1024`
#### Async/Sync Flush Checkpoint
定义如下变量:
```
checkpoint_age = redo_lsn - checkpoint_lsn
async_water_mark = 0.75 * total_redo_log_file_size
sync_water_mark = 0.9 * total_redo_log_file_size
```
假设定义了2个redo log文件并且每个文件大小为1G那么`total_redo_log_file_size`的大小为2G.
那么,`async_water_mark``1.5G``sync_water_mark``1.8G`.
-`checkpoint_age < async_water_mark`时,不需要触发任何刷新操作
-`async_water_mark < checkpoint_age < sync_water_mark`时,触发`async flush`从flush列表中刷新`足够`的脏页回磁盘
> 刷新足够脏页回磁盘,是指刷新后满足`checkpoint_age < sync_water_mark`
- `checkpoint_age > sync_water_mark`,会触发`sync_water_mark`,刷新足够的脏页回磁盘
`sync/async flush checkpoint`操作同样放入到了page cleaner线程中不会阻塞用户操作
> redo异步刷新的水位线为`0.75`,同步刷新的水位线为`0.9`
>
> 当`redo_lsn - checkpoint`的大小超过异步或同步水位线时,会把足够的脏页刷新到磁盘中,刷新后满足`redo_lsn - checkpoint < async_water_mark`
#### Dirty Page too Much
如果buffer中存在的脏页数量过多那么会触发innodb强制进行脏页刷新将脏页刷新到磁盘。
## Buffer pool刷新
innodb会在后台将脏页刷新到磁盘中。在innodb中buffer pool刷新由page cleaner thread来执行。
> ### page cleaner threads
> page cleaner threads的数量由`innodb_page_cleaners`变量来控制,该变量存在默认值,默认值为`innodb_buffer_pool_instances`的值。
### innodb_max_dirty_pages_pct_lwm
当buffer pool中的脏页比例达到`innodb_max_dirty_pages_pct`的百分比时将会触发buffer pool刷新操作。
`innodb_max_dirty_pages_pct_lwm`的默认值为`10%`如果将该变量的值设置为0那么将会禁用该刷新行为。
当配置`innodb_max_dirty_pages_pct_lwm`变量时,应该确保该变量的值小于`innodb_max_dirty_pages_pct`的值。
### innodb_lru_scan_depth
`innodb_lru_scan_depth`该变量制定了每个缓冲池实例中page cleaner在扫描lru列表时待刷新脏页的深度。该后台操作由page cleaner thread每秒执行一次。
> 若增加`innodb_lru_scan_depth`的值在用户线程IO的基础上会额外增加IO的负载。只有在工作负载之外存在空闲IO容量时才考虑增加该变量的值。
>
> 如果工作负载已经令IO容量饱和那么可以考虑减少`innodb_lru_scan_depth`的大小。
>
> `该变量默认值大小为1024`。
## 自适应刷新
innodb采用了自适应的缓冲区刷新算法根据redo log的生成速率和当前刷新频率动态调整刷新速率。通过该算法可以令刷新活动和当前的工作负载保持平衡。
通过自适应刷新,可以避免因`buffer flush产生的IO活动挤占普通读写活动的IO容量`造成的吞吐量下降。
自适应刷新根据buffer pool中的脏页数量以及redo log日志的生成速率来帮助避免`buffer flush`造成的io活动突然增加避免出现工作负载的突然变化。
### innodb_adaptive_flushing_lwm
` innodb_adaptive_flushing_lwm`变量针对redo log容量定义了一个`low water mark`,当超过该阈值时,自适应花心将会被启用,`即使innodb_adaptive_flushing变量被禁用`
### innodb_adaptive_flushing
通过`innodb_adaptive_flushing`变量,可以控制自使用刷新是否被启用,默认情况下其被启用。
## Log Buffer
log buffer为内存中的区域用于保存将要写入到磁盘log文件中的数据。log buffer的大小通过`innodb_log_buffer_size`来进行配置,默认其值为`64MB`
log buffer中的内容将会被定期刷新到磁盘中`当log buffer足够大时事务在提交之前无需将redo log内容刷到磁盘中`
### innodb_flush_log_at_trx_commit
`innodb_flush_log_at_trx_commit`用于控制如何将log buffer中的内容刷新到磁盘中。
默认情况下,该变量的值为`1`代表在事务提交时会将log buffer中的redo log内容刷新到磁盘为文件中。
### innodb_flush_log_at_timeout
`innodb_flush_log_at_timeout`控制刷新频率。
## Master Thread
master thread中的操作主要分为两部分
- 每秒钟执行一次的操作
- 每10s执行一次的操作
### 每秒钟执行一次的操作
master thread中每秒一次的操作包括如下内容
- 将log buffer中的内容刷新到磁盘即使对应事务尚未提交总是
- 合并插入缓冲(可能)
> 对于redo log即使事务当前尚未提交innodb仍然会每秒将log buffer中的内容刷新到缓冲区中
> 合并插入缓冲并不是每秒都会发生innodb会根据前1s内发生的IO次数是否小于5来判断是否合并插入缓冲。如果小于5次那么当前IO负载较小可以执行合并缓冲操作。
### 每10秒执行的操作
- 合并至多5个插入缓冲总是
- 将日志缓冲刷新到磁盘(总是)
- 删除无用的undo log页总是
## change buffer
change buffer为一个特殊的数据结构当想要修改的辅助索引page不在缓冲区中时会将针对辅助索引page的修改缓冲到change buffer中。
对于辅助索引页面的修改可能是由`insert, update, delete`操作造成的当后续待修改的页面被读入到缓冲区中时会对change buffer中缓冲的修改操作进行合并。
### 聚簇索引和辅助索引插入顺序
和聚簇索引不同,辅助索引通常不是唯一的。`并且,当使用`auto_increment`的聚簇索引时新数据的插入通常都是顺序的插入的多个数据唯一同一page中。`
但是当插入新数据时新数据对应辅助索引记录的插入则相对是无序的多条辅助索引记录的插入可能都分布在不同的page。同样的针对辅助索引记录的修改和删除其影响的辅助索引记录页可能分布在不同的索引页中。
通过change buffer将针对辅助索引页面的修改缓冲在buffer中并且当需要修改的辅助索引页面读取到缓冲区中时再对change buffer中的修改操作进行合并这样能很大程度上避免多次随机读取辅助索引页到缓冲区中带来的开销。
> 定期执行的purge操作会将被更新的索引页写入到磁盘中。相比于马上将被修改的索引页写入到磁盘中purge操作更高效。
当存在很多被修改的行数据以及辅助索引数据时change buffer merging操作可能花费数个小时来执行。在change buffer merging期间磁盘IO将会增加可能会导致磁盘相关查询的性能下降。
在内存中change bufer占用的缓冲区的一部分而在磁盘中change buffer是system tablespace的一部分。当数据库实例未启动时针对辅助索引的修改存储在磁盘的change buffer文件中。
> 当辅助索引中包含`descending index column`或主键中包含`descending index column`时change buffer不适用。
降序索引示例如下:
```sql
CREATE TABLE t (
c1 INT, c2 INT,
INDEX idx1 (c1, c2 DESC),
INDEX idx2 (c1 DESC, c2),
INDEX idx3 (c1 DESC, c2 DESC)
);
```
### change buffer配置
当对于table执行insert、update、delete一些列操作时这些操作集合中辅助索引列的值通常是无序的分散在多个辅助索引页中如果大量更新多个索引页。`当待更新的辅助索引页不位于buffer pool中时针对辅助索引页的修改将会被缓存。当待更新的索引页面被加载到buffer pool中时被缓存的修改操作将会被合并被更新的页面后续会刷新到磁盘中`
> innodb在数据库实例空闲或slow shutdown时会进行change buffer的合并。
#### 优势
change buffer可以带来更少的磁盘读取和磁盘写入故而在工作负载收到io限制时change buffer能发挥重大作用。例如在存在大量插入操作时change buffer能够显著提升性能。
#### 弊端
由于change buffer会占用buffer pool的内存空间故而当change buffer较大时会减少数据缓存的可用空间。`但是如果work set的大小和buffer pool几乎相当时或是数据库表中几乎没有辅助索引时此时则可以禁用change buffer`
### `innodb_change_buffering`
`innodb_change_buffering`变量控制chnage buffer的行为通过`innodb_change_buffering`,可以对如下操作进行启用和禁用:
- insert operations
- delete operations:`当index record被标记为删除`
- purge operations: `当index被物理删除`
> update操作是insert操作和delete操作的组合
`innodb_change_buffering`取值如下:
| value | numeric value | description |
| :-: | :-: | :-: |
| none | 0 | `默认`,不针对任何操作进行缓存 |
| inserts | 1 | 针对insert操作进行缓存 |
| deletes | 2 | 针对delete marking操作进行缓存 |
| changes | 3 | 针对inserts和delete-marking操作进行缓存 |
| purges | 4 | 针对后台发生的物理删除操作进行缓存 |
| all | 5 | 针对inserts, deletes, purges操作进行缓存 |
### `innodb_change_buffer_max_size`
`innodb_change_buffer_max_size`允许按照百分比的方式来配置change buffer占用buffer pool的最大大小默认为`25`,最大可为`50`
当mysql server存在大量insert、update、delete操作时change buffer merging操作的速率无法跟上change buffer entries的新增速率此时可以考虑增加`innodb_change_buffer_max_size`
当change buffer占用了过多buffer pool中内存导致buffer pool中页面老化速度超出预期时此时可以尝试将`innodb_change_buffer_max_size`适当调小。
## double write
double write buffer为存储区域当innodb将buffer pool中的页刷新到innodb data file中的适当位置之前会页写入到double write buffer中。
如果在写入页面的过程中发生操作系统、存储子系统、mysql进程的异常通过double write buffer在crash recovery的过程中innodb能够从double write buffer中找到page的备份。
即使数据写了两次double write buffer也不会造成两倍的IO负载或两倍的io操作数量。数据按照large chunk的方式写入到double write buffer中并且会调用`fsync`方法。
### double write结构
double write由两部分组成
- 内存: double write buffer位于内存中
- 磁盘:磁盘中的表空间
当innodb刷新buffer pool中的页面时操作如下
1. 将页面写入到double write buffer中其含有128个页并且在写入到double write buffer后调用`fsync`确保将double write buffer中的数据刷新到磁盘中
2. 在完成`1`操作后会实际将页数据写入到data file磁盘文件中再次调用`fsync`确保数据被持久化到磁盘中
在后续innodb执行recovery操作时会检查double write buffer中的内容和data file中page的内容
- 如果double write buffer中内容不一致那么仅会将double write buffer中的内容丢弃
- 如果datafile中页内容不一致那么将会从double write buffer中恢复数据
### innodb_doublewrite
`innodb_doublewrite`控制是否启用double write buffer。默认情况下double write开启。如果想要关闭double write可以将`innodb_doublewrite`设置为`OFF`.
#### DETECT_AND_RECOVERY
`DETECT_AND_RECOVERY``ON`设置相同在该设置下double write被完全启用。innodb将会将page内容写入到double write buffer中并且在recovery过程中会通过double write buffer中的数据来修复不一致page。
#### DETECT_ONLY
`innodb_doublewrite`被设置为`DETECT_ONLY`只有元数据会被写入到double write buffer数据库的page content并不会被写入到double write buffer中并且recovery过程也不会用double write buffer中的内容来恢复数据。
`DETECT_ONLY`设置只是为了检测`incomplete page write`
> 如果dobule write buffer位于支持`atomic write`的fusion-io设备上那么double write buffer将会自动被关闭datafile的写入将会使用`fusion-io atomic write`。
### innodb_doublewrite_dir
`innodb_doublewrite_dir`变量定义了innodb创建doublewrite files的位置。如果该变量没有被指定那么innodb将会把文件创建在`innodb_data_home_dir`
### innodb_doublewrite_pages
`innodb_doublewrite_pages`控制每个线程其doublewrite page的最大数量。
### innodb_doublewrite_files
`innodb_doublewrite_files`定义了doublewrite file的文件个数默认情况下值为2会为每个buffer pool instance创建两个doublewrite文件
- flush list doublewrite file
- lru list doublewrite file
#### flush list dobulewrite file
flush list doublewrite file是用于buffer pool flush list中page的刷新的。flush list doublewrite file的默认大小为`innodb page size * double write pages`
#### lru doublewrite file
lru doublewrite file是用于从buffer pool lru list中刷新的其默认大小为`innodb page size * (doublewrite pages + (512 / buffer pool instances))`
其中512为slot的总数量。
doublewirte file命名格式如下所示
```
#ib_page_size_file_number.dblwr
```