InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:
- 维护所有进程/线程需要访问的多个内部数据结构
- 缓存磁盘上的数据,方便快速读取,同时在对磁盘文件的数据修改之前在这里缓存
- 重做日志(redo log)缓冲
而后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最新的数据。此外将已经修改的数据文件刷新到磁盘文件。同时还要保证在数据库发生异常的情况下InnoDB能恢复到正常运行状态。
后台线程
InnoDB存储引擎是多线程模型,因此后台有多个不同的后台线程,负责处理不同的任务。
- Master Thread
master thread是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新,合并插入缓冲,undo页的回收等。
- IO Thread
在InnoDB存储引擎中大量使用AIO(Async IO)来处理写IO请求,这样可以极大提升数据库的性能,IO Thread就是用来处理这些IO请求的回调任务。其中IO Thread主要分为四种:write、read、insert buffer和log IO thread。
- Purge Thread
事务被提交以后,其所使用的undolog可能不再被需要,因此需要Purge Thread来回收已经使用并分配的undo页。在InndoDB存储引擎1.1版本之前,Purge的操作在Master Thread中完成,从InndDB1.1版本开始,Purge操作可以独立到单独的线程中进行,以此来减轻Master Thread的工作,从而提高CPU的利用率以及提升存储引擎的性能。
- Page Cleaner Thread
Page Cleaner Thread是在InnoDB1.2.x版本引进的,作用是将之前版本中脏页的刷新操作都放入到单独的线程中来进行,目的是减轻Master Thread的工作及对于用户查询线程的阻塞,进一步提升InnoDB存储引擎的性能。
内存
我们还需要了解一下InnoDB存储引擎中内存的结构情况,如下如所示:
InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲和额外的内存池。其中,缓冲池中缓存的数据页类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典信息等。
缓冲池
InnoDB存储引擎是基于磁盘进行存储的,并将其中的记录按照页的方式来进行管理,因此可将其视为基于磁盘的数据库系统。在数据库系统中,由于CPU与内存之间存在巨大的速度鸿沟,基于磁盘的数据库系统经常使用缓冲池技术来提高数据库的整体性能。
缓冲池简单来说就是内存中的一块区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响,在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中,下一次再读取相同的时,首先判断该页是否在缓冲池中,如果命中,则直接在缓冲池中读取。
对于数据库中页的修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上,对于页刷新回磁盘这个操作,并不是发生在每次页更新时,而是通过一种称为checkpoint的机制刷新回磁盘中,同样,这样做也是为了提升数据库的整体性能。
具体的,缓冲池中缓存的数据页类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典信息等。不能简单认为缓冲池只缓存数据页和索引页,它们只是占据缓冲池很大的一部分而已。
并且,InnoDB允许有多个缓冲池实例,每个页根据哈希值平均分配的不同的缓冲池实例中,这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。
然后我们还需要解决一个问题:缓冲池是一个很大的内存区域,里面存放着各种类型的页,InnoDB存储引擎是如何进行管理的?答案是LRU List 算法。具体来说,最频繁使用的页来LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,就要通过LRU(最近最少使用)来释放LRU列表中尾端的页。
在InnoDB存储引擎中,缓冲池中页默认大小为16kb,它的LRU算法相对来说也有一定的改进。在InnoDB存储引擎中,LRU列表中还加入了midpoint位置,新读取到的页,虽然是最新访问的页,但并不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置,在默认配置下,这个位置在LRU列表长度的八分之五处。在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表称为new列表,可以理解为new列表中的页都是最为活跃的热点数据。
不采用朴素的LRU算法的原因:
如果采用朴素的LRU算法,直接将读取到的最新的页放入到LRU列表的首部中,那么某些SQL操作可能会使缓冲池中的页被刷出,从而影响缓冲池的效率。
常见的这种操作比如:索引或数据的扫描操作,这类操作需要访问表中的许多页,甚至是全部的页,但这些页通常来说仅仅在这一次查询中起到作用,并不是活跃的热点数据,如果将这些页放入到LRU列表的首部,那么可能会导致真正的热点数据页从LRU列表中移除。
LRU列表用来管理已经读取到的页,当数据库刚启动的时候,LRU列表是空的,即没有任何的页。这时候页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则,根据LRU算法淘汰列表末尾的页,将该内存空间分配给新的页。
在LRU列表中的页被修改后,称该页为脏页,即缓冲池中的页和磁盘上的页产生了不一致。这时候数据库会通过checkpoint机制将脏页刷新回磁盘,而FLUSH列表中的页即为脏页列表。需要注意的是:脏页既存在于LRU列表中,也存在于Flush列表中,LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响。
重做日志缓冲
InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲和额外的内存池。InnoDB存储引擎首先将重做日志信息放入到这个缓冲区,然后按照一定频率将其刷新到重做日志文件中。
下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中:
- master thread每一秒将重做日志缓冲刷新到重做日志文件
- 每个事务提交时会将重做日志缓冲刷新到重做日志文件中
- 当重做日志缓冲池剩余空间小于二分之一时,重做日志缓冲刷新到重做日志文件
额外的内存池
在InnoDB引擎中,通过内存堆管理内存。在对一些数据结构本身的内存进行分配的时候,需要在额外的内存池中进行申请,当该区域的内存不够时,会从缓冲池中进行申请。例如,分配了缓冲池,但是每个缓冲池中的帧缓冲(frame buffer) 以及对应的 缓冲控制对象(buffer controll block,这些对象记录了一些诸如LRU、锁、等待等信息)这些数据对象本身的内存进行分配时,需要从额外的内存池中进行申请。
文章评论