存储引擎InnoDB体系架构
一、InnoDB简单体系架构:
1、InnoDB有多个内存块,可以认为是这些内存块组成了一个很大的内存池,负责很多工作。
- 维护所有进程/线程需要访问的内部数据结构
- 缓存磁盘上的数据,方便快速读取,并且对磁盘文件的数据在修改之前进行缓存。
- 重做日志缓存(redo log)
- ......
2、后台线程主要负责刷新内存中的数据,保证引擎内存池的数据最新;将缓存文件写入磁盘;在数据库异常情况下进行恢复。
二、后台线程介绍:
InnoDB存储引擎是在一个被称为mater Thread的的线程上几乎实现了所有的功能。InnoDB的存储引擎主要有:
- 1个master thread
- 1个锁 (lock)thread
- 1个错误监控线程
- 多个IO Thread(1个Insert Buffer、1个Log 4个 read 4g个write)
--------
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: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] ,
ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
1042832 OS file reads, 319202191 OS file writes, 236329638 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 19.23 writes/s, 16.07 fsyncs/s
三、内存
Mysql的存储引擎内存由以下部分组成:
- 缓冲池(buffer pool)
- 重做日志缓冲池(redo log buffer)
- 额外的内存池(additional memory pool)
通过命令查询一下Mysql的这些换内存的容量配置:
mysql> show variables like '%buffer_pool_size%'
;
+-------------------------+------------+
| Variable_name | Value |
+-------------------------+------------+
| innodb_buffer_pool_size | 1288699904 |
+-------------------------+------------+
mysql> show variables like '%log_buffer_size
%';
+------------------------+---------+
| Variable_name | Value |
+------------------------+---------+
| innodb_log_buffer_size | 8388608 |
+------------------------+---------+
mysql> show variables like '%_mem_pool_size
%';
+---------------------------------+---------+
| Variable_name | Value |
+---------------------------------+---------+
| innodb_additional_mem_pool_size | 2097152 |
+---------------------------------+---------+
缓冲池是占内存最大的模块,用来存放各种数据的缓存。InnoDB的工作 方式总是将数据库文件按页的形式(每页16K)读取到缓冲池,然后按最近最少使用的(LRU)算法来保留缓冲池的数据。
如果数据库文件需要修改(增、删、改),总数先在修改在缓冲池中的页(发生修改后,该页即为脏页),然后再按照一定的频率将缓冲池中的 脏页flush到文件中。可以通过以下命令来查询缓冲池的使用情况。
mysql> show engine innodb status;
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 1322123264; in additional pool allocated 0
Dictionary memory allocated 14577350
Buffer pool size 78656
Free buffers 8199
Database pages 68780
Old database pages 25226
Modified db pages 50
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 3569441, not young 98808641
0.00 youngs/s, 0.00 non-youngs/s
Pages read 1927838, created 971216, written 224962898
0.00 reads/s, 0.00 creates/s, 5.60 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: 68780, unzip_LRU len: 0
I/O sum[4352]:cur[0], unzip sum[0]:cur[0]
- Buffer pool size 代表有多少个缓冲帧(buffer frame),每个buffer frame 为16K,所以78656*16*1024 = 1288699904 就是上面命令中查询到配置 的innodb_buffer_pool_size 。
- Free buffers 代表目前空闲的缓冲帧
- DateBase pages 代表已使用的缓冲帧
- Old database pages 23600:在旧区域存放着多少个页。(新旧区域的这个比较复杂,单独再看吧)
- Modified db pages 显示是的脏页的数量
缓冲池中数据页的类型主要有:
- 索引页
- 数据页
- undo页
- 插入缓冲
- 自适应hash索引
- InnoDB存储锁信息
- 数据字典(data dictionary)
- ......
所以不能简单的人为缓冲池只是缓存索引页和数据页,下图是InnoDB存储结构图。
日志缓冲:是将重做日志信息先放到缓存区,然后按照一定的刷新频率刷新到重做日志文件。所以该配置一般不用很大,因为一般情况每秒就会刷新一下到重做日志,需要保证每秒的产生的事务量在缓冲大小之内即可
额外内存池:缓冲池中的每个帧缓冲还有对应的缓冲控制对象,而且这些对象记录了LRU,锁,等待方面等信息,都是从这里申请内存。他的大小与InnoDB缓冲池大小是成正比的。
四、Master Thread(最核心的模块了)
master thread 是最核心,线程优先级别最高的线程。其内部有几个循环组成。master Thread会根据数据库的运行状态在这几个循环之间切换。
- 主循环(loop)
- 后台循环(background loop)
- 刷新循环(flush loop)
- 暂停循环(suspend loop)
Loop循环
loop成为主循环,因为大多数的操作都在这个循环中,其中有两大部分操作:每秒钟的操作和每10秒的操作。
1、loop循环通过thread sleep来实现,意味着每1秒和每10秒的操作是不精确的,在负载很大的情况下,有很大的可能产生延迟。
2、每秒的操作主要包括
- 日志缓冲刷新到磁盘,即使事务还没有提交。这样就可以解释为什么再大的事务commit的时间都是很快的。
- 合并插入缓冲;InnoDB会判断当前一秒内发生IO的次数是否小于5次,如果小于5次,InnoDB认为当前IO压力很小,可以执行合并插入缓冲的操作。
- 至多刷新100个InnoDB缓冲池中的脏页到磁盘;InnoDB 判断当前缓冲池中的脏页比例是否超过配置文件中的比例配置(默认为90%),如果超过了这个阈值,InnoDB认为需要做磁盘同步操作,将最多100个脏页写入磁盘。
3、每10秒的操作
- 刷新100个脏页到磁盘。InnoDB会判断过去10秒之内的IO操作是否小于200,如果是人为有足够的磁盘IO能力,将100个脏页刷新到磁盘。
- 合并至多5个插入缓冲:缓冲操作不同与1秒的那个判断IO的能力,会每次都执行。
- 将日志缓冲刷到磁盘:与每1秒执行的一致。
- 删除无用的undo页:对表执行update、delte操作时,原来的行被标记为删除,当时因为一致性读的关系,需要保留这些行版本信息。此时会判定这些已经被标记的行是否可以被删除,如果可以删除会立即删除。在源码中发现每次最多删除20个undo页
- 刷新100个或者10%脏页到磁盘:判定脏页比例超过70%就刷新100个脏页,如果小于70%则刷新10个脏页到磁盘。
- 产生一个检查点(总是):称为模糊检查点(fuzzy checkpoint),因为Innodb不会把所有缓冲池中的脏页都写到磁盘,因为这样可能对性能产生影响。而只是将最老日志序列号的(oldest LSN)的页写入磁盘。
BackGround loop
若当前没有用户活动(数据库空闲)或者数据库关闭时,就会切换到这个循环,他执行这个操作:
- 删除无用的undo页
- 合并20个插入缓冲
- 调回到主循环
- 不断刷新100个页,知道符合条件(跳转到flush loop中完成)
如果flush loop中也没有事情可以做了,InnoDB就会切换到suspend_loop,将master_thread挂起,等待事件发生。
Master Thread完整的伪代码如下:
//伪代码
void master_thread(){
loop:
for(int i=0;i<10;i++{
thread.sleep(1)//每秒执行1次
do log buffer flush to disk //日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)
if(last_one_second_ios<5){ //合并插入缓冲(可能)
do merge at most 5 insert buffer
}
if(buf_get_modified_ratio_pct>innoddb_max_dirty_pages_pct){
do buffer pool flush 100 dirty page //最多刷新100个InnoDB缓冲池中的脏页到磁盘(可能)
}
if(no user activity){
goto back ground loop; //如果没有用户活动,切换到background loop(可能)
}
}
//执行第10秒的操作
if(last_ten_second_ios<200){ //IO次数小于200
do buffer pool flush 100 dirty page //最多刷新100个脏页
}
do merge at most 5 insert buffer;//合并5个缓冲页
do log buffer flush to disk //日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)
do full purge;//删除无用undo页
if(buf_get_modified_ratio_pct>70%){ //刷新脏页
do buffer pool flush 100 dirty page
}else{
do buffer pool flush 10dirty page
}
do fuzzy checkpoint;//检查点
goto loop;
backgroupd loop:
{
do full purge;//删除无用undo页
do merge 20 insert buffer;//合并5个缓冲页
if {!idle){ //判断空闲
goto loop;
}else{
goto flush loop; }
}
flush loop:
{
do buffer pool flush 100 dirty page//刷新100个脏页
if(buf_get_modified_ratio_pct>innoddb_max_dirty_pages_pct){
goto flush loop;
}
goto suspend loop;
}
suspend loop:{
suspend_thread //挂起线程
wait event;//等待事件
}
}
master Thread的潜在问题:
了解Master Thread的具体实现过程中,发现InnoDB对IO的操作是有限制的,无论何时,InnoDB最多只会刷入100个脏页到磁盘,合并20个插入缓冲。如果是密集写的应用程序,每秒可能会大于100个脏页或者是产生大于20个插入缓冲,此时master Thread 似乎会忙不过来。
InnoDB对此做发布了补丁,提供了一个参数(innodb_io_capacity)表示IO的吞吐量,默认值是200,如果磁盘的IO性能较好,可以调大该值。根据配置的IO数量,修改了上面的部分规则:
- 合并缓冲时,合并插入的缓冲数量为Innodb_io_capacity的5%。
- 从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_catpacity.
另外innoddb_max_dirty_pages_pct(脏页的比例)为90%时才进行刷新100个脏页,这个对内存的占用会较大一些,但是如果调整过小,对磁盘的IO压力变大,后期修改为了75%。同时另外提供了一个每次刷新脏页数量的配置,不再是固定的100页。