689 lines
40 KiB
Markdown
689 lines
40 KiB
Markdown
- [innodb体系结构](#innodb体系结构)
|
||
- [innodb体系结构](#innodb体系结构-1)
|
||
- [mysql存储结构](#mysql存储结构)
|
||
- [内存结构](#内存结构)
|
||
- [Buffer Pool](#buffer-pool)
|
||
- [change Buffer](#change-buffer)
|
||
- [Adaptive hash index](#adaptive-hash-index)
|
||
- [log Buffer](#log-buffer)
|
||
- [磁盘存储结构](#磁盘存储结构)
|
||
- [system tablespace](#system-tablespace)
|
||
- [后台线程](#后台线程)
|
||
- [Master Thread](#master-thread)
|
||
- [IO Thread](#io-thread)
|
||
- [Purge Thread](#purge-thread)
|
||
- [Page Cleaner Thread](#page-cleaner-thread)
|
||
- [内存](#内存)
|
||
- [缓冲池](#缓冲池)
|
||
- [缓冲池参数配置](#缓冲池参数配置)
|
||
- [innodb\_buffer\_pool\_size](#innodb_buffer_pool_size)
|
||
- [LRU List, Free List, Flush List](#lru-list-free-list-flush-list)
|
||
- [midpoint](#midpoint)
|
||
- [page made young](#page-made-young)
|
||
- [youngs/s \& non-youngs/s](#youngss--non-youngss)
|
||
- [buffer pool hit rate](#buffer-pool-hit-rate)
|
||
- [FreeList](#freelist)
|
||
- [Flush List](#flush-list)
|
||
- [redo log buffer](#redo-log-buffer)
|
||
- [checkpoint](#checkpoint)
|
||
- [缩短数据库恢复时间](#缩短数据库恢复时间)
|
||
- [缓冲池不够用](#缓冲池不够用)
|
||
- [redo log不可用](#redo-log不可用)
|
||
- [LSN](#lsn)
|
||
- [Fuzzy Checkpoint](#fuzzy-checkpoint)
|
||
- [checkpoint种类](#checkpoint种类)
|
||
- [Master Thread Checkpoint](#master-thread-checkpoint)
|
||
- [FLUSH\_LRU\_LIST\_CHECKPOINT](#flush_lru_list_checkpoint)
|
||
- [Async/Sync Flush Checkpoint](#asyncsync-flush-checkpoint)
|
||
- [Dirty Page too Much](#dirty-page-too-much)
|
||
- [Buffer pool刷新](#buffer-pool刷新)
|
||
- [innodb\_max\_dirty\_pages\_pct\_lwm](#innodb_max_dirty_pages_pct_lwm)
|
||
- [innodb\_lru\_scan\_depth](#innodb_lru_scan_depth)
|
||
- [自适应刷新](#自适应刷新)
|
||
- [innodb\_adaptive\_flushing\_lwm](#innodb_adaptive_flushing_lwm)
|
||
- [innodb\_adaptive\_flushing](#innodb_adaptive_flushing)
|
||
- [Log Buffer](#log-buffer-1)
|
||
- [innodb\_flush\_log\_at\_trx\_commit](#innodb_flush_log_at_trx_commit)
|
||
- [innodb\_flush\_log\_at\_timeout](#innodb_flush_log_at_timeout)
|
||
- [Master Thread](#master-thread-1)
|
||
- [每秒钟执行一次的操作](#每秒钟执行一次的操作)
|
||
- [每10秒执行的操作](#每10秒执行的操作)
|
||
- [change buffer](#change-buffer-1)
|
||
- [聚簇索引和辅助索引插入顺序](#聚簇索引和辅助索引插入顺序)
|
||
- [change buffer配置](#change-buffer配置)
|
||
- [优势](#优势)
|
||
- [弊端](#弊端)
|
||
- [`innodb_change_buffering`](#innodb_change_buffering)
|
||
- [`innodb_change_buffer_max_size`](#innodb_change_buffer_max_size)
|
||
- [double write](#double-write)
|
||
- [double write结构](#double-write结构)
|
||
- [innodb\_doublewrite](#innodb_doublewrite)
|
||
- [DETECT\_AND\_RECOVERY](#detect_and_recovery)
|
||
- [DETECT\_ONLY](#detect_only)
|
||
- [innodb\_doublewrite\_dir](#innodb_doublewrite_dir)
|
||
- [innodb\_doublewrite\_pages](#innodb_doublewrite_pages)
|
||
- [innodb\_doublewrite\_files](#innodb_doublewrite_files)
|
||
- [flush list dobulewrite file](#flush-list-dobulewrite-file)
|
||
- [lru doublewrite file](#lru-doublewrite-file)
|
||
- [adaptive hash index(自适应哈希)](#adaptive-hash-index自适应哈希)
|
||
- [自适应哈希](#自适应哈希)
|
||
- [自适应哈希构建](#自适应哈希构建)
|
||
- [访问模式](#访问模式)
|
||
- [AHI构建要求](#ahi构建要求)
|
||
- [non-hash searches](#non-hash-searches)
|
||
- [innodb\_adaptive\_hash\_index](#innodb_adaptive_hash_index)
|
||
- [异步io](#异步io)
|
||
- [Sync IO](#sync-io)
|
||
- [AIO](#aio)
|
||
- [io merge](#io-merge)
|
||
- [刷新邻接页](#刷新邻接页)
|
||
- [启动、关闭和恢复](#启动关闭和恢复)
|
||
- [innodb\_fast\_shutdown](#innodb_fast_shutdown)
|
||
- [innodb\_force\_recovery](#innodb_force_recovery)
|
||
|
||
|
||
# innodb体系结构
|
||
## innodb体系结构
|
||
innodb体系结构由如下部分构成
|
||
- innodb存储引擎内存池
|
||
- 后台线程
|
||
- 磁盘上的文件数据
|
||
|
||
innodb内存池由多个内存块构成,内存快负责如下功能:
|
||
- 维护进程、线程访问的内部数据结构
|
||
- 对磁盘上的文件数据进行缓存(加快读取速度),同时缓存对磁盘数据的修改
|
||
- redo log缓冲
|
||
|
||
在innodb存储引擎中,后台线程主要负责刷新内存池中的数据,保证内存缓存为最新状态。此时后台线程还负责将内存池中的修改刷新到磁盘中。
|
||
|
||
## mysql存储结构
|
||
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 buffer),undo页的回收等。
|
||
|
||
### 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将会被自动调整到10G,10G是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
|
||
LSN(log sequence number)为日志序列号, 是一个全局单调递增的64位整数,`类似于innodb内部的逻辑时钟),是全局唯一的`。
|
||
|
||
LSN随着redo log的不断产生而单调递增。当发生数据的修改时,会生成redo log,此时LSN也会增加。
|
||
|
||
### 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
|
||
```
|
||
|
||
## adaptive hash index(自适应哈希)
|
||
自适应哈希允许innodb在不牺牲传统食物特性以及可靠性的基础上,在工作负载适当,buffer pool内存充足的场景下,表现的更像in-memory database。
|
||
|
||
### 自适应哈希
|
||
哈希的查找性能较B+树来说较高,B+树的查找次数取决于B+树的高度,生产环境中B+树的高度通常为3~4层,故而需要3~4次查询,而哈希通常只需要一次查找就能定位到数据。
|
||
|
||
#### 自适应哈希构建
|
||
innodb监控索引查询,并且当观测到建立哈希索引能够加速查询时,会自动创建哈希索引,故而成为自适应哈希索引(AHI)。
|
||
|
||
AHI是通过buffer pool中的`B+树页`来构建的,故而构建数度很快。`AHI并不需要根据整张表构建哈希索引`,innodb会根据访问频率和模式来自动为某些热点页建立哈希索引。
|
||
|
||
#### 访问模式
|
||
AHI在监控索引访问时,要求页的连续访问模式必须是一样的。例如,对于`(a,b)`这样的联合索引页,其访问模式存在如下可能场景:
|
||
- where a = xxx
|
||
- where a = xxx and b = xxx
|
||
|
||
上面两种场景都会走`(a,b)`联合索引,但是在AHI看来其访问模式并不相同。
|
||
|
||
#### AHI构建要求
|
||
在满足对索引页的连续访问模式一样后,AHI的构建还有如下要求:
|
||
- 以相同模式访问了100次
|
||
- 待构建的索引页通过该模式访问了N次,`N = 页中的记录数 / 16`
|
||
|
||
即对于制定索引,既要求访问该索引的条件相同,也要求索引页被多次访问(索引页被访问次数达到页中记录数的1/16)
|
||
|
||
在启用AHI后,读取和写入速度可以提升2~3倍,辅助索引连接性能可以提升5倍。
|
||
|
||
#### non-hash searches
|
||
hash索引只能被用于等值查询,对于其他类型查找,例如范围查找并不适用。
|
||
|
||
### innodb_adaptive_hash_index
|
||
是否启用自适应哈希索引可以通过`innodb_adaptive_hash_index`变量来控制,当值为`ON`时,代表自适应哈希索引被启用。
|
||
|
||
hash index是通过index key的前缀来进行构建的,prefix可以是任何长度,并且只有b树中的一些值能够出现在hash index中。`hash index是为经常被访问的索引页面按需进行构建的`。
|
||
|
||
如果一张表几乎全部都加载到了内存中,那么通过hash index能够加速对该表的查询。此时,hash index对应的value类似于指针,能够直接访问表中的任何元素。
|
||
|
||
> innodb中存在监控索引搜索的机制,如果innodb发现可以通过构建hash index来受益,那么其会字段能够构建hash索引。
|
||
|
||
在某些工作负载下,hash index带来的查找加速远远超过了`监控索引搜索`以及`维护hash index结构`带来的开销。
|
||
|
||
但是,在某些高工作负载的场景下,hash index可能并不会带来收益,此时,可以关闭hash index从而减少额外开销。
|
||
|
||
adaptive hash index是被分区的,每个hash index被绑定到一个分区,并且每个分区被一个独立的latch保护。
|
||
|
||
分区数量可以通过`innodb_adaptive_hash_index_parts`来控制,默认为8,最大可以设置为512.
|
||
|
||
可以通过`show engine innodb status`中输出的` SEMAPHORES`部分来监控adaptive hash index。如果存在许多线程正在等待`btr0sea.c`中创建的rw latches,可以考虑增加adaptive hash index partitions或者禁用adaptive hash index。
|
||
|
||
## 异步io
|
||
为了提升数据库性能,innodb采用异步io操作方式。
|
||
|
||
### Sync IO
|
||
传统的Sync IO要求每进行一次io操作,都要求当前io操作执行完成后才能执行下一次io操作。
|
||
|
||
对于同步阻塞io,当用户发送了一条索引扫描的查询指令时,那么该sql可能需要扫描多个索引页面,每次索引页面的扫描需要一次IO操作。sync io需要每次索引扫描完成后再执行下一次索引扫描。
|
||
|
||
### AIO
|
||
对于异步io,用户可以在发送一个io指令后立刻再发送另一个io指令,并在所有io指令发送后等待所有io操作完成。发送另一个io指令时,并不需要等待前面io操作的完成,这样可以降低在io阻塞上等待的时间。
|
||
|
||
#### io merge
|
||
aio除了可以无阻塞的执行多条io操作外,还可以进行io merge操作,即将多个io合并为一个io。通过将多个io请求合并为一个io请求,可以提升iops(每秒钟执行的io数量)。
|
||
|
||
例如,用户需要访问的页(space, page)为`(8,6), (8,7), (8,8)`,每个页的大小为16kb,那么当使用同步io时,需要执行3次io操作。
|
||
|
||
当时,当使用aio时,aio会判断`(8,6), (8, 7), (8,8)`这是那个页是连续的,那么aio底层只会发送一个io请求,从(8,6)开始,读取48KB大小的数据。
|
||
|
||
通过linux命令`iostat`,能够观察`rrqm/s`和`wrqm/s`(read/write requests merged per second)。
|
||
|
||
## 刷新邻接页
|
||
innodb中提供了刷新邻接页的特性。当刷新脏页时,innodb会检测页所在区(extent)中的所有页,如果也是脏页,则会一起执行刷新操作。
|
||
|
||
通过刷新邻接页的操作,可能将不怎么脏的脏页也进行了刷新,而该页和快又被变脏;而且,在使用固态硬盘时,固态硬盘拥有较高的iops,可能并不太需要该特性。
|
||
|
||
可以通过`innodb_flush_neighbors`变量来控制是否刷新邻接页。
|
||
|
||
## 启动、关闭和恢复
|
||
### innodb_fast_shutdown
|
||
在innodb进行关闭时,`innodb_fast_shutdown`参数影响innodb引擎的行为,该参数可选值为`0,1,2`,默认值为1。
|
||
|
||
参数各值代表的行为如下:
|
||
- 0: mysql数据库关闭时,innodb需要完成所有full purge和merge insert buffer,并且要将所有的脏页刷新到磁盘中。这需要很长时间来完成,`如果在对innodb进行升级时,需要将这个参数调整为0,并关闭数据库`
|
||
- 1: 默认值,代表不需要完成full purge和merge insert buffer操作,但是要将脏页刷新会磁盘
|
||
- 2: 代表不完成full purge和merge insert操作,也不将脏页刷新回磁盘,而是将日志都写到日志文件中,这样不会有任何事物的丢失,但是下次mysql启动时,需要执行恢复操作(recovery)
|
||
|
||
### innodb_force_recovery
|
||
参数innodb_force_recovery控制innodb引擎的恢复情况。该参数默认为0,代表需要恢复时进行所有恢复操作;并且当不能有效恢复时,例如数据页发生冲突,会令mysql宕机,并且将错误写入到错误日志中。
|
||
|
||
innodb_force_recovery的取值如下:
|
||
- 0:默认
|
||
- 1(SRV_FORCE_IGNORE_CORRUPT):忽略检查到的冲突页
|
||
- 2(SRV_FORCE_NO_BACKGROUND):阻止master thread线程的运行,如master thead需要进行full purge,这会导致crash
|
||
- 3(SRV_FORCE_NO_TRX_UNDO):不进行事务的回滚
|
||
- 4(SRV_FORCE_NO_IBUF_MERGE):不进行插入缓冲的合并操作
|
||
- 5(SRV_FORCE_NO_UNDO_LOG_SCAN):不查看undo log,innodb会将未提交事务看作已提交
|
||
- 6(SRV_FORCE_NO_LOG_REDO):innodb不执行前滚操作
|
||
|
||
> 如果在数据库发生宕机时,存在未提交事务,那么将下次数据库启动时,会先根据未提交事务的undo log来对数据进行回滚。
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|