148 lines
7.5 KiB
Markdown
148 lines
7.5 KiB
Markdown
# innodb体系结构
|
||
## innodb体系结构
|
||
innodb体系结构由如下部分构成
|
||
- innodb存储引擎内存池
|
||
- 后台线程
|
||
- 磁盘上的文件数据
|
||
|
||
innodb内存池由多个内存块构成,内存快负责如下功能:
|
||
- 维护进程、线程访问的内部数据结构
|
||
- 对磁盘上的文件数据进行缓存(加快读取速度),同时缓存对磁盘数据的修改
|
||
- redo log缓冲
|
||
|
||
在innodb存储引擎中,后台线程主要负责刷新内存池中的数据,保证内存缓存为最新状态。此时后台线程还负责将内存池中的修改刷新到磁盘中。
|
||
|
||
## 后台线程
|
||
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部分页的访问是由用户发起的操作,那么那么该页面会被移动到young部分的头部
|
||
- 随着数据库的运行,LRU中的young部分和old部分页面都会向LRU列表尾部移动,这被称为`老化`。当其他页面触发`made young`操作时:
|
||
- 如果发生`made young`操作,那么young部分和old部分的节点都会向后移动,发生老化
|
||
- 如果由新页面被插入到midpoint,那么只有old部分的节点会发生老化
|
||
|
||
#### FreeList
|
||
当数据库实例刚启动时,LRU里列表中并没有任何页,此时页都存放在Free List中。当要从缓冲池中获取页时,首先查看Free List中是否有空闲的页,`如果有则从FreeList中获取,并将该页添加到LRU的midpoint位置`;`若Free List中没有空闲的页,那么将根据LRU算法淘汰LRU尾部的页,将淘汰页的内存空间分配给新的页。`
|
||
|