1.
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存是最近的数据。此外,将已经修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常情况下InnoDB能恢复到正常运行状态。
InnoDB存储引擎时在一个被称作master thread的线程上几乎实现了所有的功能。
默认情况下,InnoDB存储引擎的后台线程有7个——4个IO thread, 1个master thread,1个锁监控线程,1个错误监控线程。
IO thread的数量由配置文件中的innodb_file_io_threads参数控制,默认为4.
show engine innodb status\G
可以看到4个线程分别是insert buffer thread, log thread, read thread, write thread.
InnodB Plugin版本开始增加了默认IO thread的数量,默认的read thread 和write thread分别增大到了4个,并且不再使用innodb_file_io_threads参数,而是分别使用innodb_read_io_threads和innodb_write_io_threads参数。
show variables like 'innodb_version'\G
show variables line 'innodb_%io_threads';
2.
InnoDB存储引擎内存由以下几个部分组成:缓冲池(buffer pool),重做日志缓冲池(redo log buffer),以及额外的内存池(additional memory pool),分别由配置文件中参数innodb_buffer_pool_size和innodb_log_buffer_size决定。
show variables like 'innodb_buffer_pool_size';
show varianles like 'innodb_log_buffer_size';
show variables like 'innodb_additional_mem_pool_size';
InnoDB的存储引擎的工作方式是将数据库文件按页(每页16K)读取到缓冲池,然后按最近最少使用(LRU)算法来保留在缓冲池中的缓存数据。如果数据库文件需要修改,总是首先修改在缓存池中的页(发生修改后,该页即为脏页),然后再按照一定的频率将缓冲池的脏页刷新到文件。
buffer pool size表明了一共共有多少个缓存帧(buffer frame),每个buffer frame为16K,所以一共分配了8192*16/1024。Free buffers表示当前空闲的缓冲帧,Database pages表示已经使用的缓冲帧。Modified db pages表示脏页的数量。
具体来看,缓冲池中缓存的数据页类型有:索引页,数据页,undo页,插入缓冲(insert buffer),自适应哈希索引(adaptive hash index),InnoDB存储的锁信息,数据字典信息(data dictionary)等。不能简单的认为,缓冲池只是缓存索引页和数据页,它们只是占缓冲池很大一部分而已。
3.
日志缓冲将重做日志信息先放入缓冲区,然后按一定的频率将其刷新到重做日志文件。需要保证每秒产生的事务量在这个缓冲大小之内即可。
在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆的方式进行的。在对一些数据结构本身分配内存时,需要从额外的内存池中申请内存,当该区域的内存不够时,会从缓冲池中申请。
4.
master thread的线程优先级别最高,其内部由几个循环组成:主循环、后台循环、刷新循环、暂停循环。master thread会根据数据库运行的状态在这几个循环中进行切换。
主循环,包括两大部分操作:每秒钟的操作和没10秒钟的操作。
void master_thread()
{
loop:
for(int i = 0; i < 10; i++) {
do thing once per second sleep 1 second if necessary
}
do things once per ten seconds
goto loop;
}
每秒一次的操作包括:
日志缓冲刷新到磁盘,即使这个事物还没有提交(总是)
合并插入缓冲(可能)
至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)
如果当前用户没有活动,切换到background loop。(可能)
即使某个事务还没有提交,InnoDB存储引擎任然会每秒将重做日志缓冲中的内容刷新到重做日志。
当前1秒内发生的IO次数是否小于5次,如果小于,可以执行合并插入缓冲的操作。
InnoDB存储引擎判断当前缓冲池中脏页的比例(buf_get_modified_radio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数,如果超过这个阀值, Innodb认为需要磁盘同步操作,将100个脏页写入磁盘。
每10秒的操作:
刷新100个脏页到磁盘(可能)
合并至多5个插入缓冲(总是)
将日志缓冲刷新到磁盘(总是)
删除无用的Undo页(总是)
刷新100个或10个脏页到磁盘(总是)
产生一个检测点(总是)
InnoDB存储引擎会先判断过去10秒之内磁盘的IO是否小于200次,如果是,将100个脏页刷新到磁盘。
存储引擎会执行一步full purge操作,即删除无用的undo页。对表标记update,delete这类操作时,原先的行为被标记为删除,但是因为一致性读的关系,需要保留这些行版本的信息。但在full purge过程中,InnoDB会判断当前事务系统中已被删除的行是否可以删除,比如有时候还有查询操作需要读取之前版本的undo信息,如果可以,InnoDB会立即将其删除。
然后,InnoDB会判断缓冲池中脏页的比例,如果有超过70%的脏页,则刷新100个脏页到磁盘;如果小于70%,则只需要刷新10%的脏页到磁盘。
最后,InnoDB会产生一个检查点,InnoDB的检查点也称为模糊检查点。
void master_thread()
{
goto loop;
loop:
for(int i = 0; i < 10; i++) {
thread_sleep(1);
do log buffer flush to disk
if(last_one_second_ios < 5)
do merge at most 5 insert buffer
if(buf_get_modified_ration_pct > innodb_max_dirty_pages_pct)
do buffer pool flush 100 dirty page
if(no user activity)
goto background loop
}
if(last_ten_second_ios < 200)
do buffer pool flush 100 dirty pages
do merge at most 5 insert buffer
do log buffer flush to disk
do full purge
if(buf_get_modified_ratio_pct > 70%)
do buffer pool flush 100 dirty page
else
buffer pool flush 10 dirty page
do fuzzy checkpoint
background loop:
do something
goto loop:
}
接下来看background loop, 若当前没有用户活动(数据库空闲)或者数据库关闭时,
删除无用的undo页(总是)
合并20个插入缓冲(总是)
跳回到主循环(总是)
不断刷新100个页,直到符合条件(可能,跳转到flush loop中完成)。
若flush loop 中也没有事情可做了,InnoDB存储引擎会切换到suspend_lop,将master thread挂起,等待事件的发生。
void master_thread()
{
goto loop;
loop:
for(int i = 0; i < 10; i++) {
thread_sleep(1);
do log buffer flush to disk
if(last_one_second_ios < 5)
do merge at most 5 insert buffer
if(buf_get_modified_ration_pct > innodb_max_dirty_pages_pct)
do buffer pool flush 100 dirty page
if(no user activity)
goto background loop
}
if(last_ten_second_ios < 200)
do buffer pool flush 100 dirty pages
do merge at most 5 insert buffer
do log buffer flush to disk
do full purge
if(buf_get_modified_ratio_pct > 70%)
do buffer pool flush 100 dirty page
else
buffer pool flush 10 dirty page
do fuzzy checkpoint
goto loop
background loop:
do full purge
do merge 20 insert buffer
if not idle:
goto loop:
else:
goto flush loop
flush loop:
do buffer pool flush 100 dirty page
if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
goto flush loop
goto suspend loop
suspend loop:
suspend_thread()
waiting event
goto loop;
}
5.
InnoDB Plugin开始提供了一个参数,用来表示磁盘IO的吞出量,参数innodb_io_capacity,默认值为200.对于刷新磁盘的数量,会按照innodb_io_capacity的百分比来刷新相对数量的页。
在合并插入缓冲时,合并插入缓冲的数量为innodb_io_capacity数值的5%。
在从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_capacity.
InnoDB Plugin带来的另一个参数是innodb_adaptive_flusing(自适应刷新),该值影响每1秒刷新脏页的数量。
原来的刷新规则是:如果脏页在缓冲池所占的比例小于innodb_max_dirty_pages_pctt时,不刷新脏页。大于innodb_max_dirty_pages_pct时,刷新100个脏页,而innodb_adaptive_flushing参数的引入,InnoDB会通过一个名为buf_flush_get_desired_flush_rate的函数来判断需要刷新脏页最合适的数量。该函数是通过判断产生重做日志的速度来判断最合适刷新脏页的数量。因此,当脏页的比例小于innodb_max_dirty_pages_pct时,也会刷新一定量的脏页。
void master_thread() {
goto loop;
loop:
for(int i = 0; i < 10; i++) {
thread_sleep(1);
do log buffer flush to disk
if(last_one_second_ios < 5% innodb_io_capacity)
do merge 5% innodb_io_capacity insert buffer
if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
do buffer pool flush 100% innodb_io_capacity dirty page
else if enable adaptive flush
do buffer pool flush desired amount dirty page
if(no user activity)
goto background loop
}
if(last_ten_second_ios < innodb_io_capacity)
do buffer pool flush 100% innodb_io_capacity dirty page
do merge at most 5% innodb_io_capacity insert buffer
do log buffer flush to disk
do full purge
if(buf_get_modified_ration_pct > 70% innodb_io_capacit)
do buffer pool flush 100% innodb_io_capacit dirty page
else
do buffer pool flush 10% innodb_io_capacit dirty page
do fuzzy checkpoint
goto loop
background loop:
do full perge
do merge 100% innodb_io_capacity insert buffer
if not idle:
goto loop:
else:
goto flush loop
flush loop:
do buffer pool flush 100% innodb_io_capacity dirty page
if(buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct)
goto flush loop
goto suspend loop
suspend loop:
suspend_thread()
waiting event
goto loop;
}
6.
InnoDB存储引擎的关键特性包括插入缓冲,两次写,自适应哈希索引。
InnoDB缓冲池中中Insert Buffer和数据页一样,也是物理页的一个组成部分。
不可能每张表上只有一个聚集索引,在更多的情况下,一张表上有多个非聚集的辅助索引。例如
create table t (id int auto_increment, name varchar(30), primary key(id), key(name));
这种情况下产生了一个非聚集的并且不是唯一的索引。
InnoDB的插入缓冲,对于非聚集索引的插入或更新操作,不是每一次直接插入索引页。而是先判断插入的非索引页是否在缓冲池中。如果在,则直接插入;如果不在,则先放入一个插入缓冲区中,好似欺骗数据库这个非聚集的索引已经插到叶子节点了,然后再以一定的频率执行插入缓冲和非聚集索引叶子节点的合并操作。这时通常能将多个插入合并到一个操作中。
插入缓冲需要满足两个条件:
索引是辅助索引
索引不是唯一的
不是唯一的,因为在把它插入到插入缓冲时,我们并不去查找索引页的情况。如果去查找肯定会出现离散读的情况,插入缓冲就失去了意义。
seg size显示了当前插入缓冲的大小,free list len代表空闲列标配的长度,size代表了已经合并记录页的数量, insert代表插入的记录数。
如果说插入缓冲带给InnoDB存储引擎的是性能,那么两次写带给InnoDB存储引擎的是数据的可靠性。当数据库宕机时,可能发生数据库正在写一个页面,而这个页面只写了一部分的情况,我们称为写失效。在应用重做日志前,我们需要一个页的副本,当写入失效时,先通过页的副本来还原该页,再进行重做,这就是doublewrite。
doublewrite由两部分组成:一部分是内存中的doublewrite buffer,另一部分是物理磁盘上共享空间中连续的128个页。
当缓冲池的脏页刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先拷贝到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次写入1MB到共享空间的物理磁盘上,然后调用fsync函数,同步磁盘。在完成doublewrite页的写入后,再将doublewrite buffer中的页写入各个表空间文件中,此时的写入是离散的。
show global status like 'innodb_dblwr'\G
如果操作系统在将页写入磁盘的过程崩溃了,在恢复过程中,InnoDB可以从共享表空间中的doublewrite中找到该页的一个副本,将其拷贝到表空间文件,再应用重做日志。
InnoDB会监控对表上索引的查找,如果观察到建立哈希索引可以带来速度的提升,则建立哈希索引,称为自适应的。自适应哈希索引通过缓冲池的B+树构造而来,因此建立的速度很快,而且不需要将整个表都建立哈希索引,会根据访问的频率和模式来为某些页建立哈希索引。