一、Master Thread
Master Thread具备最高的线程优先级。其内部有
主循环(loop)
后台循环(backgroup loop)
刷新循环(flush loop)
暂停循环(suspend loop)
多个循环(loop)组成。Master Thread会根据数据库的状态在这些循环之间切换。Loop被称为主循环,因为大多数的操作都是在这个循环中,其中有两大部分的操作:每秒钟的操作和每10秒的操作
每秒钟的操作包含以下内容:
重做日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)
合并插入缓冲(可能)
至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)
如果当前没有用户活动,这切换到BackGround loop(可能)
我们简单的解读一下“每秒中的操作”:
即使某个事务还没有提交,InnoDB存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这可以很好的解释为什么再大的事务提交的时间也是很短的。
合并插入缓冲并不是每秒都会发生的。InnoDB存储引擎会判断当前一秒内发生的IO次数是否小于5次,如果小于5次,InnoDB认为当前IO的压力很小,可以执行合并插入缓冲的操作。
刷新100个脏页也不是每秒都会发生的。InnoDB通过判断当前缓冲池中脏页的比例(buf_get_modified_ratio_pct)是否超过了配置文件中的最大脏页的百分数innodb_max_dirty_pages_pct这个动态参数,如果超过了这个阈值,InnoDB认为需要做磁盘同步的操作,将100个脏页写入磁盘中,这个参数不建议调整。
mysql> show variables like '%innodb_max_dirty_pages_pct%';
+--------------------------------+-----------+
| Variable_name | Value |
+--------------------------------+-----------+
| innodb_max_dirty_pages_pct | 90.000000 |
| innodb_max_dirty_pages_pct_lwm | 10.000000 |
+--------------------------------+-----------+
2 rows in set (0.01 sec)
每10秒的操作包含以下内容:
刷新100个脏页到磁盘(可能的情况下)
合并至多5个插入缓冲(总是)
删除无用的undo页(总是)
刷新100个或者10个脏页到磁盘(总是)
我们来解读一下“每十秒操作”的整个过程:
首先InnoDB会先判断过去十秒之内磁盘的IO操作是否小于200次,如果是,InnoDB认为当前有足够的磁盘IO操作能力,因此将100个脏页刷新到磁盘。
接着,InnoDB会合并插入缓冲。不同于“每秒一次操作”时可能发生的合并插入缓冲操作,这次的合并插入缓冲一定会发生。之后,InnoDB会再进行一次重做日志缓冲刷新到磁盘的操作。
接着InnoDB会进行一步执行full purge的操作,即删除无用undo页。但是在删除前会判断是否有查询操作需要读取之前版本的undo信息,如果没有,这可以删除,但每次最大回收20个undo页。
然后,InnoDB会判断缓冲池中脏页的比例(buf_get_modified_ratio_pct),如果有超过70%的脏页,则刷新100个脏页到磁盘,如果脏页比例小于70%,则只需刷新10个脏页到磁盘。
若当前没有用户活动(数据库空闲时)或者数据库关闭,就会切换到background loop执行以下下操作:
删除无用的Undo页(总是)
合并20个插入缓冲(总是)
跳回到主循环(总是)
不断刷新100个页直到符合条件(可能,跳转到flush loop中完成)
若flush loop中也没有什么事情可以做了,InnoDB会切换到suspend_loop,将Master Thread 挂起,等待事件发生。若用户启用了InnoDB引擎,但是没有创建InnoDB的表,那么Master Thread总是处于挂起状态。
简单总结一下这四种循环的切换过程:
首先会进入主循环,执行每一秒操作和每十秒操作,其中先循环执行每一秒操作的逻辑,当计算器大于等于10时,就进入每十秒操作的逻辑,然后在跳回到“每一秒操作”的逻辑,一直这样循环下去,直到没有用户活动或数据库关闭,会跳到background loop的执行逻辑。
接着background loop的逻辑执行完毕,如果有用户活动,这跳入主循环;如果没有用户活动,则跳到flush loop循环。
接着flush loop的逻辑执行完毕,会跳入suspend_loop(暂停循环)。
最后暂停循环中会将Master Thread挂起,等待事件唤醒。 有新的事件发生时,MasterThread被唤醒,重复1~4的步骤。
以上的逻辑是InnoDB1.0.x版本之前中Master Thread的执行逻辑,在后来的1.0.x和1.1.x版本中对Master Thread的功能做了一些优化,来提升Master Thread的执行效率,进行提升InnoDB的性能,主要优化内容如下:
从之前的版本中我们可以看出,无论何时,InnoDB最大只会刷新100个脏页,合并20个插入缓冲,这是硬编码的。但是随着固态硬盘的出现大大提升了磁盘的写入性能,如果每次刷新脏页和合并插入缓冲的数量不变的话,就会造成磁盘写性能的浪费。特别是对于写入密集的应用程序,每秒可能会产生大于100个的脏页,如果是产生大于20个插入缓冲的情况,Master Thread似乎会“忙不过来”,或者说它总是做得很慢。同时,当发生宕机需要恢复时,由于很多数据还没有刷新回磁盘,会导致恢复的时间可能需要很久,尤其是对于insert buffer来说。
因此从InnoDB1.0.x开始,提供了动态变量innodb_io_capacity,来表示磁盘IO的吞吐量,默认为200。执行规则如下:
在合并插入缓冲时,合并插入缓冲的数量为innodb_io_capacity值为5%;
在从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_capacity.
mysql> show variables like '%innodb_io_capacity%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| innodb_io_capacity | 200 |
| innodb_io_capacity_max | 2000 |
+------------------------+-------+
2 rows in set (0.00 sec)
当用户采用SSD或其它提高IO性能的措施后,可调整innodb_io_capacity直到符合磁盘IO的吞吐量(IPOS),但是不建议超过20000。
set persist innodb_io_capacity=300
Innod1.0.x版本还有增加了一个参数是innodb_adaptive_flushing(自适应刷新。默认开启),该值影响每秒刷新脏页的数量。具体规则:InnoDB会通过判断产生重做日志(redo log)的速度来决定最合适的刷新脏页数量。因此,当脏页的比例小于innodb_max_dirty_pages_pct时,也会刷新一定量的脏页。
还有一个改变是:在InnoDB1.0.x版本开始引入innodb_purge_batch_size(默认值300),用于控制每次full purge操作时回收Undo页的数量。并且从InnoDB1.1版本开始,purge操作独立到单独的线程(purge Thread)中进行,以此来减轻Master Thread的工作。
在InnoDB1.2.x版本中又进行了优化,主要优化内容如下:
这个版本中最大的优化就是:对于刷新脏页的操作,从Master Thread线程分离到一个单独的Page Cleaner Thread,从而减轻了Master Thread的工作,同时进一步提高了系统的并发性。
二、IO Thread
在InnoDB中大量使用了AIO(Async IO)异步IO来处理写请求,这样可以极大的提高数据库的性能。而IO Thread的工作主要是负责这些IO请求的回调处理。在InnoDB1.0版本之前共有4个IO Thread,分别是write、read、inser buffer和log IO Thread。从InnoDB1.0.x版本开始,read thread和write thread分别增大到了4个,分别使用innodb_read_io_threads和innodb_write_io_threads参数进行设置,并且读线程ID总是小于写线程。
mysql> show variables like '%io_threads%';
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_read_io_threads | 4 |
| innodb_write_io_threads | 4 |
+-------------------------+-------+
2 rows in set (0.01 sec)
异步IO是InnoDB很重要的一个特性,也是提升性能的一个重要功能。与AIO对应的是Sync IO,即每进行一次IO操作,需要等待此次操作结束才能继续接下来的操作。但是如果用户发出的是一条索引扫描的查询,那么这条SQL查询语句可能需要扫描多个索引页,也就是需要进行多次的IO操作。在每扫描一个页并等待其完成后再进行下一次的扫描,这是没有必要的。用户可以发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有IO操作的完成,这就是AIO。
AIO的另一个优势是可以进行IO Merge操作,也就是将多个IO合并为1个IO,这样可以调高IOPS的性能。
三、Purge Thread
Purge Thread是InnoDB1.1版本开始新加入的线程,专门用于处理回收不再需要的undo页的工作,以此来减轻Master Thread的工作,从而提高CPU的使用率以及提升存储引擎的性能。在Mysql配置文件中,通过Innodb_purge_threads参数来设置线程数。但是在InnoDB1.1版本中,即使设置了此参数大于1,在InnoDB启动时也会将其设为1。在InnoDB1.2版本开始,InnoDB支持多个Purge Thread,默认4个这样做的目的是为了加快undo页的回收。同时由于Purge Thread需要离散地读取undo页,这样也能更进一步利用磁盘的随机读取性能。
mysql> show variables like '%Innodb_purge_threads%';
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| innodb_purge_threads | 4 |
+----------------------+-------+
1 row in set (0.00 sec)
四、Page Cleaner Thread
Page Cleaner Thread是INnoDB1.2版本开始引入的。其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。其目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB的性能