最核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、UNDO页的回收等。
内部由多个循环(loop)组成:
主循环(loop) 后台循环(backgroup loop) 刷新循环(flush loop) 暂停循环(suspend loop)
Master Thread会根据数据库运行的状态在不同的状态之间切换。
InnoDB引擎大量使用了AIO(Async IO)处理写IO请求,极大地提高了数据库的性能。IO Thread的工作是负责这些IO请求的回调(call back)处理
共四个IO Thread,分别是write、read、insert buffer和log IO thread
Thread数量可通过参数调整
事务被提交后,其所使用的undo log不再需要,PurgeThread回收已经使用并分配的undo页
完成脏页的flush(刷入磁盘)操作
InnoDB存储引擎基于磁盘,并按页进行管理。由于CPU速度与磁盘速度之间的鸿沟, 基于磁盘的数据库系统通常使用内存缓冲池技术来提高数据库性能。
缓冲池就是一块内存区域,通过内存的速度来弥补磁盘速度。数据库每次读取页时,首先判断该页是否在缓冲池中。若在,直接使用。否则读磁盘。
对于修改操作,则首先修改缓冲池中的页,然后再以一定的频率写回磁盘。需要注意的是,写回磁盘的操作并不是每次修改缓冲池中的页都会触发,而是通过Checkpoint机制间隔写回磁盘。也是为了提高数据库的整体性能。
缓冲池中缓存的数据页类型有很多,包括:
索引页 数据页 undo页 插入缓冲(insert buffer) 自适应哈希索引(adaptive hash index) InnoDB存储的锁信息(lock info) 数据字典信息(data dictionary)等
因为缓冲池是有限的,且大多数情况下, 缓冲池都显著小于数据库大小,因此必须使用一种有效的算法管理缓冲池,保证缓冲池的更新
缓冲池中页大小默认为16KB,innoDB通过LRU算法管理缓冲池中的页
按照传统LRU算法,会将新读取到的页放入到LRU的首部,但某些SQL操作可能会使缓冲池中真正常用的页被刷出,从而影响缓冲池的效率。比如索引或数据的扫描操作。这类操作需要访问表中的许多页,而这些页仅在这次查询操作中需要,并不是活跃的热点数据。如果使用传统LRU,就会将热点数据从LRU列表中移除。
因此InnoDB没有使用传统LRU算法,而是做了一些优化。
在InnoDB的存储引擎中,LRU加入了midpoint位置。新读取到的页,虽然是最新访问的页,但并不放入到LRU首部,而是放入到LRU列表的midpoint位置。默认配置下, 该位置在LRU列表长度的5/8处
同时InnoDB还引入了另一个参数来进一步管理LRU列表innodb_old_blocks_time
,用于表示页读取到midpoint后需等待多久才会被加入到LRU列表的热端。因此当需要执行上述所说的SQL操作时,可以通过设置这两个参数保证热点数据不会被刷出
LRU列表管理已经读取的页,当数据库刚启动时,LRU列表是空的。此时页都在Free列表中。当有新数据需要使用缓冲池时,先从Free列表看是否有可用空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页。
把页从LRU的old部分加入到new部分的操作叫做page made young,而因为innodb_old_blocks_time
的设置而导致页没有从old移动到new的操作称为page not made young
。
LRU列表中的页被修改后,称为脏页(dirty page),即缓冲池中的页和磁盘上的页数据不一致。这时数据库会通过CHECKPOINT
机制将脏页刷新回磁盘,而Flush List即为脏页列表。脏页既存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,Flush列表管理将页刷新回磁盘,二者互不影响。
InnoDB存储引擎首先将重做日志信息放入到undo log缓冲区,然后按一定频率将其刷新到重做日志文件。重做日志缓冲不需要设置得很大,因为一般情况下每秒都会将重做日志缓冲不断地刷回日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。该值可由配置参数innodb_log_buffer_size
控制,默认为8MB
页的操作首先都是在缓冲池中完成的。如果一条DML语句改变了页记录,那么此时页是脏的,数据库需要将新版本的页从缓冲池写回到磁盘。
但每次发生变化,就写磁盘,开销太大。若热点数据集中在某几个页,那么数据库的性能将变得非常差。同时,如果写回磁盘时发生了宕机,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了Write Ahead Log策略,当事务提交时,先写重做日志,再修改页。如果发生宕机,就通过重做日志进行数据恢复。
Checkpoint(检查点)技术的目的是解决以下几个问题:
当数据库发生宕机时,数据库不需要重做所有的日志,Checkpoint之前的页都已经刷回磁盘。只需对Checkpoint后的重做日志进行恢复。这样大大缩短了恢复的时间
当缓冲池不够用时,根据LRU算法会选出淘汰页,若此页为脏页,那么需要强制执行Checkpoint,将脏页刷回磁盘。
重做日志文件都是循环使用的,当环形队列满时,就需要强制写回部分数据回数据库
Sharp Checkpoint
Sharp Checkpoint发生在数据库关闭时,此时会将所有的脏页都刷新回磁盘
Master Thread Checkpoint
Master Thread 会定时将一部分脏页写回,大概是10s左右一次
FLUSH_LRU_LIST Checkpoint
保证LRU列表中需要有指定大小的空闲页可供使用,倘若没有,那InnoDB存储引擎会将LRU列表尾端的页移除。如果这些页中有脏页,那就需要进行Checkpoint
Async/Sync Flush Checkpoint
指的是重做日志文件用满的情况,为了保证重做循环日志的可用性,需要强制将一些页刷新回磁盘,此时脏页是从脏页列表中选取的。
Dirty Page too much Checkpoint
缓冲池中脏页太多时会启动,以前是90%,现在是75%
由多个状态构成
主循环(loop) 后台循环(backgroup loop) 刷新循环(flush loop) 暂停循环(suspend loop)
Master Thread会根据数据库运行的状态在这几个状态之间进行切换。
大多数的操作在主循环中,分为两大部分的操作
每秒钟的操作和每10秒的操作
每秒一次的操作包括:
日志缓冲刷新到磁盘,即使这个事务还没有提交
即使某个事务还没有提交,InnoDB存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这是为什么再大的事务提交(commit)的时间也是很短的。
合并插入缓冲
刷新部分脏页到磁盘
如果当前没有用户活动,则切换到background loop
每10秒的操作
如果切换到background loop也没事干,就切换到suspend loop
提升非聚集索引插入的性能
插入缓冲不是缓冲池中的一个组成部分。InnoDB缓冲池中当然有Insert Buffer的缓冲数据,但是Insert Buffer和数据页一样,是物理页的一个组成部分。
一般情况下, 聚集索引插入时,数据都是有序的(一般主键都是有序递增的),不需要磁盘的随机读取,性能还不错,但是非主键索引(辅助索引)并不是顺序存放的,因此插入数据后,更新辅助索引时,需要离散地访问辅助索引页,性能下降明显,因此有了插入缓冲。
对于辅助索引的插入、更新操作,不是直接插入到索引页中,而是先判断插入的 辅助索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到Insert Buffer中,然后再以一定的频率进行Insert Buffer和辅助索引页子节点的merge操 作,通常能将多个插入合并到一个操作中(因为在一个索引页中), 大大提高了辅助索引插入的性能
使用Insert Buffer需满足以下两个条件:
索引是辅助索引(secondary index)
索引不是唯一索引
唯一索引插入时需要查磁盘判断字段是否唯一,失去了缓冲的意义
可视为Insert Buffer的加强。InnoDB可以对DML操作—— INSERT、DELETE、UPDATE都进行缓冲,分别是:Insert Buffer
、Delete Buffer
、Purge buffer
和Insert Buffer一样,Change Buffer适用的对象也是非唯一的辅助索引
对一条记录进行UPDATE操作分为两个过程:
Insert/Change Buffer是一棵 B+树。若待插入的辅助索引页不在缓冲池中,那就将辅助索引记录首先插入到这棵B+树中。根据设定好的条件,待之后进行更新
插入B+树的节点格式如下
InnoDB如何得知需要合并的辅助索引页呢?
在Insert Buffer B+树中,辅助索引页根据(space,offset)都已排序好,故可以根据(space,offset)的顺序进行页的选择。
发生数据库宕机时,如果InnoDB正在写入某个页到表中,而且只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效。
也许你会想,发生写失效,可以通过重做日志进行恢复。这是一个办法。但重做日志记录的是对页的物理操作,如偏移量800,写'aaaa'记录。如果页本身已经损坏,再进行重做是没有意义的。就是说,在应用重做日志前,需要一个页的副本,当写入失效发生时,先通过页的副本还原该页,再进行重做,这就是doublewrite。
在对缓冲池的脏页进行刷新时,不直接写磁盘,而是通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后再写回,如果写回失败,那就利用buffer存储的信息恢复后重新尝试写回,
InnoDB会监控对表上各索引页的查询。如果观察到某个条件的查询特别频繁,则建立自适应哈希索引,自适应哈希索引是通过缓冲池的B+树页构造而来,因此建立的速度很快,不需要对整张表构建哈希索引。
AHI有一个要求,即对这个页的查询的条件必须是一样的。例如对于(a,b)这样的联合索引页,其访问模式可以是以下情况:
WHERE a=xxx
WHERE a=xxx and b=xxx
若交替进行上述两种查询, 那么InonDB不会对该页构造AHI
如果用户发出的是一条索引扫描的查询,那么这条SQL查询语句可能需要扫描多个索引页,每次只发出扫描一个页的请求并等待其完成后再进行下一次的扫描是没有必要的。用户可以在发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有IO操作的完成.
AIO的优势是可以进行IO Merge操作,也就是将多个IO合并为1个IO,提高性能。例如用户需要访问页的(space, page_no)为:
(8,6)、(8,7),(8,8)
每个页的大小为16KB,那么同步IO需要进行3次IO操作。而AIO会判断到这三个页是连续的(显然可以通过(space,page_no)得知)。 因此AIO底层会发送一个IO请求,从(8,6)开始,读取48KB的页。