1.前言
innodb存储引擎的主要工作都是在一个单独的后台线程Master Thread中完成的,这一节将具体解释该线程的具体实现及该线程可能存在的问题。
2.innodb 1.0.x版本之前的 Master Thread
Master thread 具有最高的线程优先级别。其内部由多个循坏(loop)组成:主循环(loop)、后台循坏(backgroup loop)、刷新循坏(flush loop)、暂停循坏(suspend loop) Master Thread 会根据数据库运行的状态在loop、background loop、flush loop和suspend loop中进行切换。
Loop被称为主循环,因为大多数的操作是在这个循坏中,其中有两大部分的操作--每秒中的操作和每10秒的操作,
void master_thread(){ loop: for(int I=0; I<10; I++){ do thing once per second sleep 1 second if necessary } do thing once per ten seconds goto loop; }
可以看到,loop循坏通过thread sleep 来实现,这意味着所谓的每秒一次或者每10秒一次的操作是不精确的,在负载很大的情况下可能会有延迟(delay),只能说大概在这个频率下。当然,innodb源代码中还通过了其他的方法来尽量保证这个频率。
每秒一次的操作包括:
- 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)
- 合并插入缓冲(可能)
- 至多刷新100个innodb的缓冲池中的脏页到磁盘(可能)
- 如果当前没有用户活动,则切换到background loop(可能)
即使某个事务还没有提交,innodb存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这一点是必须要知道的,因为这可以很好地解释为什么再大的事务提交(commit)的时间也是很短的。
合并插入缓冲(insert buffer)并不是每秒都会发生的。innodb存储引擎会判断当前一秒内发生的iO次数是否小于5次,如果小于5次,innodb认为当前io压力很小,可以执行合并插入缓冲的操作。
同样,刷新100个脏页也不是每秒都会发生的,innodb存储引擎通过判断当前缓冲池中脏页的比例(buf_get_modified_ratio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数,如果超过了,innodb存储引擎认为需要做磁盘同步的操作,将100个脏页写入磁盘中。
每10秒的操作包括:
- 刷新100个脏页到磁盘(可能的情况下)
- 合并至多5个插入缓冲(总是)
- 将日志缓冲刷新到磁盘(总是)
- 删除无用的Undo页(总是)
- 刷新100个或10个脏页到磁盘(总是)
在以上的过程中,innodb存储引擎会先判断过去10秒之内磁盘的IO操作是否小于200次,如果是,innodb存储引擎认为当前有足够的磁盘io操作能力,因此将100个脏页刷新到磁盘。接着,innodb存储引擎会合并插入缓冲,不同于每秒一次操作时可能发生的合并插入缓冲操作,这次的合并插入缓冲操作总会在这个阶段进行。之后innodb存储引擎会再进行一次将日志缓冲刷新到磁盘的操作。这和每秒一次时发生的动作是一样的。
接着innodb存储引擎会进行一步执行full purge操作,即删除无用的undo页。对表进行update、delete这类操作时,原先的行被标记为删除,但是因为一致性读(consistent read)的关系,需要保留浙西行版本的信息。但是在full purge过程中,innodb存储引擎会判断当前事务系统中已被刹车农户的行是否可以删除,比如有时候可能还有查询操作需要读取之前版本的undo信息,如果可以删除,innodb会立即将其删除。从源代码中可以发现,innodb存储引擎在执行full purge操作时,每次最多尝试回收20个undo页。
然后,innodb存储引擎会判断缓冲池中脏页的比例(buf_get_modified_ratio_pct),如果有超过70%的脏页,则刷新100个脏页到磁盘,如果脏页的比例小于70%,则只需要刷新10%的脏页到磁盘。
如下为主循环(main loop)的伪代码:
接着来看backuground loop,若当前没有用户活动(数据库空间时)或者数据库关闭(shutdown),就会切到这个循坏,background loop会执行以下操作:
- 删除无用的Undo页(总是)
- 合并20个插入缓冲(总是)
- 跳回到主循环(总是)
- 不断刷新100个页知道复合条件(可能,跳转到flush loop中完成)
若flush loop中也没有什么事情可做了,innodb存储引擎会切换到suspend_loop,将Master thread挂起,等待事件发生。若用户启动(enable)了innodb存储引擎,且没有使用任何innodb存储引擎的表,那么Master Thread总是处于挂起的状态。
3.innodb1.2.x版本之前的Master thread
在了解了1.0.x版本之前的Master Thread的具体实现过程后,细心的读者会发现innodb存储引擎对于IO其实是有限制的,在缓冲池向磁盘刷新时其实都做了一定的硬编码(hard coding)。在磁盘技术飞速发展的今天,当固态磁盘(SSD)出现时,这种规定在很大程度上限制了innodb存储引擎对磁盘IO的性能,尤其是写入性能。
从前面的伪代码来看,无论和何时,innodb存储引擎最大只会刷新100个脏页到磁盘,合并20个插入缓冲。Master Thread似乎会‘忙不过来’,或者说它总是做的很慢。即使磁盘能在1秒内处理多于100个页的写入和20个插入缓冲的合并,但是由于hard conding,master thread也只会选在刷新100个脏页和20个插入缓冲。同时,当发生宕机需要恢复时,由于很多数据还没有刷新回磁盘,会导致恢复时间可能需要很久,尤其是对insert buffer来说。
但是上述问题在innodb开发团队的优化下进行改写,因此从innodb1.2.x版本开始提供了参数innodb_io_capacity,用来表示磁盘IO的吞吐量,该值默认200,对于刷新到磁盘页的数量,会按照innodb_io_capacity的百分比来进行控制。规则如下:
- 在合并插入缓冲时,合并插入缓冲的数量为innodb_io_capacity值的5%
- 在从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_capacity.
root@localhost 10:41: [information_schema]> show variables like ‘innodb_io_capacity‘; +--------------------+-------+ | Variable_name | Value | +--------------------+-------+ | innodb_io_capacity | 200 | +--------------------+-------+ 1 row in set (0.00 sec)
若用户使用了SSD类的磁盘,或者将几款磁盘做了RAID,当存储设备拥有更高的IO速度时,完全可以将Innodb_io_capacity的值调的再高一些,直到符合磁盘IO的吞吐量为止。
关于innodb_max_dirty_pages_pct参数,在innodb 1.0.x之前的版本,是90%,但是由于该值太大,因此从innodb1.0.x开始,该值默认是75%, 最后经过google测试的80%的性能是最好的
innodb1.0.x版本带来的另一个参数是innodb_adaptive_flushing(自适应刷新),该值影响每秒刷新脏页的数量。原来的刷新规则是:脏页在缓冲池所占用的比例小于innodb_max_dirty_pages_pct时,不刷新脏页,大于innodb_max_dirty_pages_pct刷新100个脏页。随着innodb_adaptive_flushing参数的引入,innodb存储引擎会通过名为buf_flush_get_desired_flush_rate通过判断产生重做日志(redo log)的速度来决定最合适刷新脏页数量,因此,当脏页的比例小于innodb_max_dirty_pages_pct时,也会刷新一定量的脏页
还有一个改变量,之前每次进行full purge操作时,最多回收20个Undo页,从innodb1.0.x版本开始引入参数innodb_purge_batch_size,该参数可以控制每次full page回收的Undo页的数量,该参数的默认值为300.
很多测试都显示,innodb1.0.x版本在性能方面取得了极大的提高,其实这个前面的Master thread的改动是密不可分的,因为innodb存储引擎的核心操作大部分都集中在Master Thread后台线程中。
root@localhost 11:12: [information_schema]> show engine innodb status\G; *************************** 1. row *************************** Type: InnoDB Name: Status: ===================================== 2021-09-05 11:12:25 0x7ff4ac099700 INNODB MONITOR OUTPUT ===================================== Per second averages calculated from the last 1 seconds ----------------- BACKGROUND THREAD ----------------- srv_master_thread loops: 2 srv_active, 0 srv_shutdown, 79591 srv_idle srv_master_thread log flush and writes: 79593
4.innodb1.2.x版本的Master Thread
在innodb 1.2.x版本中再次对Master Thread进行了优化,由此也可以看出Master Thread对性能所起到的关键性作用,在innodb 1.2.x版本中,Master thread的伪代码如下:
if innodb is idle srv_master_do_idle_tasks(); else srv_master_do_active_tasks();
其中srv_master_do_idle_tasks()就是之前版本中每10秒的操作,srv_master_do_active_tasks()处理的是之前每秒的操作,同时对于刷新脏页的操作,从Master Thread线程分离到一个单独的page cleaner thread 从而减轻了Master thread的工作,同时进一步提高了系统的并发性。