master thread是InnoD存储引擎非常核心的一个在后台运行的主线程,相当重要。它做的主要工作包括但不限于:将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲等。
master thread的线程优先级别最高,其内部由几个循环组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。master thread会根据数据运行的状态在loop、background loop、flush loop和suspend loop这4个循环之间进行切换。loop称为主循环,因为大多数的操作都在这个循环中,其中有两大部分操作:每秒钟的操作和每10秒的操作。
loop循环通过thread sleep来实现,这意味着所谓的每秒一次或每10秒一次的操作是不精确的。在负载很大的情况下可能会有延迟,只能说大概在这个频率下。当然InnoDB源代码中还采用了其他的方法来尽量保证这个频率。
每秒一次的操作包括:
需要注意的是,即使某个事务还没有提交,InnoDB存储引擎仍然会每秒将重做日志缓冲中的内容刷新到重做日志文件。这一点是必须知道的,可以很好地解释为什么再大的事务提交的时间也是很快的。合并插入缓冲(insert buffer)并不是每秒都发生,InnoDB存储引擎会判断当前一秒内发生的I/O次数是否小于5次,如果小于5次,InnoDB会认为当前的I/O压力很小,可以执行合并插入缓冲的操作。同时InnoDB存储引擎通过判断当前缓冲池中的脏页比例(buf_get_modified_ratio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数来决定是否需要进行磁盘同步操作,如果超过了这个阈值,InnoDB存储引擎认为需要做磁盘同步操作。
每10秒的操作包括如下内容:
这里需要注意的是,不同于1秒操作时可能发生的合并插入缓冲操作,这次的合并插入缓冲操作总会在这个阶段进行。InnoDB存储引擎会再执行一次将日志缓冲刷新到磁盘的操作,与每秒发生的操作是一样的。InnoDB存储引擎会执行一步full purge操作,即删除无用的undo页。对表执行update、delete这类操作时,原生的行被标记为删除,但是因为一致性读的关系,需要保留这些行版本的信息,但是在full purge过程中,InnoDB存储引擎会判断当前事务系统中已被删除的行是否可以删除,比如有时可能还有查询操作需要读取之前版本的undo信息,如果可以,InnoDB会立即将其删除。
在InnoDB存储引擎中大量使用了AIO(Async I/O)来处理写I/O请求,这样可以极大地提高数据库的性能。I/O线程的工作主要是负责这些I/O请求的回调(call back)处理。InnoDB 1.0版本之前共有4个I/O线程,分别是write、read、insert buffer和log I/O thread。在Linux平台下,I/O线程的数量不能进行调整,但是在Windows平台下可以通过参数innodb_file_io_threads来增大I/O线程。从InnoDB 1.0.x版本开始,read thread和write thread分别增大到了4个,并且不再使用innodb_file_io_threads参数,而是分别使用innodb_read_io_threads和innodb_write_io_threads参数进行设置,如此调整后,在Linux平台上就可以根据CPU核数来更改相应的参数值了。
假如CPU是2颗8核的,那么可以在配置文件my.cnf中设置:
MySQL 5.6版本以前,脏页的清理工作交由master线程处理。page cleaner thread是5.6.2版本引入的一个新线程,实现从master线程中卸下缓冲池刷脏页的工作。为了进一步提升扩展性和刷脏效率,在5.7.4版本里引入了多个page cleaner线程,从而达到并行刷脏的效果。
如图2-9所示,如果调整参数innodb_page_cleaners,需要在配置文件my.cnf中添加innodb_page_cleaners=num值(默认是1;最大可以是64,也就是会有64个page cleaner线程并发清理脏页)。
图2-9
如何判断是否要修改innodb_page_cleaners呢?如图2-10所示,参数Innodb_buffer_pool_wait_free标志着脏页有没有成为系统的性能瓶颈,如果它的值很大,则需要增加innodb_page_cleaners值,同时增加写线程。
图2-10
通常,对于Buffer Pool的写发生在后台,当InnoDB需要读或创建一个数据页但是没有干净的可用页时,InnoDB就会为使等待的操作能完成而先将一些脏页刷入磁盘。Innodb_buffer_pool_wait_free就是等待操作的实例数。如果innodb_buffer_pool_size的大小设置适当,这个值就会很小,甚至为0。
purge thread负责回收已经使用并分配的undo页。由于进行DML语句的操作都会生成undo,因此系统需要定期对undo页进行清理,可使用purge操作。
为什么MySQL InnoDB需要purge操作呢?明确这个问题的答案,首先还得从InnoDB的并发机制开始。为了更好地支持并发,InnoDB的多版本一致性读采用了基于回滚段的方式。另外,对于更新和删除操作,InnoDB并不是真正地删除原来的记录,而是设置记录的delete mark为1。为了解决数据page和undo log膨胀的问题,需要引入purge机制进行回收。
purge数据产生的背景是undo log和mark deleted数据:
(1)undo log保存了记录修改前的镜像。在InnoDB存储引擎中,undo log分为insert undo log和update undo log。insert undo log是指在insert操作中产生的undo log。由于insert操作的记录只是对本事务可见,其他事务不可见,因此undo log可以在事务提交后直接删除,不需要purge操作。update undo log是指在delete和update操作中产生的undo log。该undo log会被后续用于MVCC当中,因此不能在提交的时候删除。提交后会放入undo log的链表,等待purge线程进行最后的删除。
(2)当我们删除数据行时,对数据页中要删除的数据行做标记“deleted”,事务提交(速度快);后台线程purge线程对数据页中有“deleted”标签的数据行进行真正的删除。
purge操作默认是在master thread中完成的。从MySQL 5.6开始,为了减轻master thread的工作、提高CPU使用率以及提升存储引擎的性能,把purge thread单独从master thread分离出来,已经支持多个purge线程同时进行。提供了参数innodb_purge_threads来控制做purge操作的后台线程数,从MySQL 5.7.8开始,这个参数默认是4,如果实例的写压力比较大,则可调整innodb_purge_threads=8,增加并发purge线程数,最大可以设置为32。