主要包含以下几部分:
1、管理服务于工具组件。
2、连接池与鉴权。
3、SQL接口。
4、查询分析器。
5、优化器组件。
6、缓存与缓冲区。
7、各式的插件式存储引擎。
8、物理文件。
InnoDB作为MySQL架构中的插件式存储引擎,提供了一系列标准的管理和服务支持对物理文件的操作。
二、InnoDB体系架构
如下是InnoDB的架构模型。InnoDB存储引擎由后台线程组和内存池两部分构成。
后台线程
主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。此外,将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常情况下InnoDB能恢复到正常运行状态。.后台线程包括:多个IO thread,1个master thread,1个锁(lock)监控线程,1个错误监控线程。
master thread
master thread是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页面的刷新、合并插入缓冲、undo页的回收等。
io thread
在InnoDB引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。而io thread的主要工作就是负责这些IO请求的回调处理。可以使用命令show engine innodb status来观察io thread。
从上图可以看到,有1个insert buffer线程;1个log线程;4个read线程;4个write线程,可以通过innodb_read_io_threads和innodb_write_io_threads参数进行设置。
purge thread
事务被提交后,其所使用的undo log(用于事务commit失败后回滚操作用)可能不在需要,因此需要purge线程来回收undo页。可以通过参数innodb_purge_threads进行配置。
page cleaner thread
在InnoDB 1.2.x版本中引入,其作用是将之前版本中脏页面是刷新操作都放在独立的线程中完成,也是为了减轻master thread的压力,提升性能。
内存
InnoDB存储引擎内存由以下几个部分组成:缓冲池(buffer pool)、重做日志缓冲池(redo log buffer)以及额外的内存池(additional memory pool),分别由配置文件中的参数innodb_buffer_pool_size和innodb_log_buffer_size的大小决定。
缓冲池
InnoDB引擎的数据存储是基于磁盘的,把记录按照一定的格式记录在磁盘,但是由于CPU与磁盘的速度有较大的差别,因此引入了基于内存缓冲技术来提高数据库的性能。简单来讲,把要读取数据的磁盘内容先加载到内存缓冲区域,下次读取同样数据时先判断是否被缓冲区所缓存,如果缓存则从缓冲区读取内容;同样,当需要对数据进行修改时,不直接修改磁盘对应数据,而是先修改缓冲区域,然后通过一种叫checkpoint的机制把更新的数据刷新到磁盘。
因此对于InnoDB引擎来讲,缓冲池的设置变得尤其重要,可以通过innodb_buffer_pool_size参数进行设置。
虽然缓冲池是为了缓冲数据,但是缓冲池保存的数据类型不仅仅只有数据库的记录,有以下几种类型:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引、InnoDB存储的锁信息(lock info)、数据字典信息等。
从InnoDB 1.0.x版本开始,允许有多个缓冲池实例,每个页根据哈希值平均分配到不同缓冲池实例中,这样可以增加数据库的并发处理。 配置innodb__buffer_pool__instances将缓冲池分割为多个实例。
内存管理 LRU List、Free List、Flush List
通常来说,数据库中的内存缓冲区是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理。即频繁使用的页在LRU列表的前端,而最少使用的页在LRU的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表尾端的页。
在InnoDB引擎中,缓冲池中页的大小默认是16KB,同样也是使用LRU算法来进行管理,稍有不同的是,引擎对传统的LRU算法进行了一些优化,当读取到新的页,但并不是直接插入到LRU列表的首部,而是插入到LRU列表的midpoint位置,这个位置可以通过参数innodb_old_blocks_pct控制,默认情况下这个数值是37,代表插入到LRU列表尾部的37%的位置。
介绍完LRU List,下面介绍Free List,Free列表保存的是空闲页,引擎需要从缓冲池中分页时,首先从Free列表中查找是否有空闲页,若有则把页从Free List获取然后删除放到LRU List中,同理当LRU List的页面需要淘汰时则重新加入到Free List。
在LRU列表中的页被修改后,程该页为脏页(dirty page),即缓冲池中的页和磁盘上的页数据产生了不一致,这时候数据库会通过checkpoint机制将脏页刷新回磁盘,而Flush List则是专门保存这些脏页的列表。
redo log重做日志
InnoDB存储引擎的内存区域除了缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将日志信息先放入到这个缓冲区,然后按一定的频率刷新到redo log文件中。
额外的内存池
额外的内存池记录的是数据库内部需要用到的各种数据结构,比如记录缓冲池信息的、记录LRU、锁的信息。
checkpoint
每次执行update、delete,insert都会使缓冲池中的页与磁盘不一致,但是缓冲池的页不能频繁刷新到磁盘中(频率过大性能低),因此增加了write ahead log策略,当事务提交时先写redo log,再修改内存页。当发生宕机时通过重做日志来恢复。checkpoint技术就是用于记录redo log里面,那些log已经刷新到磁盘了,那些log还没刷新。checkpint解决以下问题:
(1)减少重做日志大小,缩减数据恢复时间。
(2)缓冲池不够用时将脏页刷回磁盘。
(3)重做日志不可用时将脏页刷回磁盘(如写满)。
可以通过innodb_max_dirty_pages_pct参数设置checkpoint进行刷新。
三、InnoDB关键特性
关键特性包括:
(1) Insert buffer.
(2) double write.
(3) adaptive hash index.
(4) Async IO.
(5)Flush neighbor page.
Insert buffer
若插入按照聚集索引primary key插入,页中的行记录按照primary存放,一般情况下不需要读取另一个页记录,插入速度很快(如果使用UUID或者指定的ID插入而非自增类型则可能导致非连续插入导致性能下降,由B+树特性决定)。对于非聚集索引的插入或更新操作,不是每一次直接插入索引页中。而是先判断插入的非聚集索引页是否在缓冲池中。如果在,则直接插入;如果不在,则先放入一个插入缓冲区中,好似欺骗数据库这个非聚集的索引已经插到叶子节点了,然后再以一定的频率执行插入缓冲和非聚集索引页子节点的合并操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对非聚集索引执行插入和修改操作的性能。。使用insert buffer需满足条件:
(1)索引为辅助索引。
(2)索引非唯一。(唯一索引需要从查找索引页中的唯一性,可能导致离散读取)
Double write
Doubel write保证了页的可靠性,Redo log是记录对页(16K)的物理操作,若innodb将页写回表时写了一部分(如4K)出现宕机,则物理页将会损坏无法通过redolog恢复。所以在apply重做日志前,将缓冲池中的脏页通过memcpy到doublewrite buffer中,再将doublewrite buffer页分两次每次1MB刷入共享表空间的磁盘文件中(磁盘连续,开销较小),完成doublewrite buffer的页写入后再写入各个表空间的表中。
当写入页时发生系统崩溃,恢复过程中,innodb从共享表空间的doublewrite找到该页的副本,并将其恢复到表空间文件中,再apply重做日志。
Adaptive hash index
Innodb根据访问频率对热点页建立哈希索引,AHI的要求是对页面的访问模式必须一样,如连续使用where a='xxx' 访问了100次。建立热点哈希后读取速度可能能提升两倍,辅助索引连接性能提升5倍。
通过show engine innodb status\G;查看hash searches/s, 表示使用自适应哈希,对于范围查找则不能使用。
Async IO
用户执行一次扫描如果需要查询多个索引页,可能会执行多个IO操作,AIO可同时发起多个IO请求,系统自动将这些IO请求合并(如请求数据页[1,2]、[2,3]则可合并为从1开始连续扫描3个页)提高读取性能。
刷新临近页
InnoDB提供刷新临近页功能:当刷新一脏页时,同时检测所在区(extent)的所有页,如果有脏页则一并刷新,好处则是通过AIO特性合并写IO请求,缺点则是有些页不怎么脏也好被刷新,而且频繁的更改那些不怎么脏的页又很快变成脏页,造成频繁刷新。对于固态磁盘则考虑关闭此功能(将innodb_flush_neighbors设置为0)。
四、主线程(Master Thread)的工作过程
Master thread的线程优先级别最高。其内部由几个循环(loop)组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。master thread会根据数据库运行的状态在loop、background loop、 flush loop和suspend loop中进行切换。
主循环Loop有两大操作——每秒和每10秒,并且是通过线程的sleep实现循环的,可见当负载很大时,会出现延迟的情况,所以并不是很精确的每秒和每10秒操作
每秒的操作如下:
- 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)。
- 合并插入缓冲(可能)。
- 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)。
- 如果当前没有用户活动,切换到background loop(可能) 。
每10秒的操作如下:
- 刷新100个脏页到磁盘(可能)。
- 合并至多5个插入缓冲(总是)。
- 将日志缓冲刷新到磁盘(总是)。
- 删除无用的Undo页(总是)。
- 刷新100个或者10个脏页到磁盘(总是)。
- 产生一个检查点checkpoint(总是)。
当数据库空闲或者数据库关闭时,就会切换到后台循环Background Loop,其操作有:
删除无用的undo页
合并20个插入缓冲
跳回到主循环
跳到刷新循环flush loop
刷新循环Flush Loop只做了一件事:刷新页到缓冲池
若刷新循环也没事情做了,InnoDB就会切换到暂停循环Suspend Loop,此时Master Thread就挂起并等待事件的发生
以上是InnoDB 1.0版本之前的操作,可以发现主线程中最多一次刷新100个脏页到磁盘,合并20个插入缓冲,如果在数据量密集的项目中,每秒产生的脏页可能远远大于100,这就会造成主线程忙不过来而变得很慢。同时发生宕机并恢复时,因为很多数据没有写到磁盘,所以恢复时间会大大延长。故InnoDB 1.2版本对主线程Master Thread进行了优化。
InnoDB 1.2通过提供参数innodb_io_capacity的配置来解决该问题,其值默认为200
从缓冲区刷新脏页时,刷新脏页数量为innodb_io_capacity的值
在合并插入缓冲时,合并数量为innodb_io_capacity的5%,即默认为40
如果固态硬盘SSD等高速硬盘,可以通过提高该参数的值来提高主线程的处理能力
另外上面说到,每秒循环中在脏页比例大于参数innodb_max_dirty_pages_pct的值时(默认90%),才会刷新100个脏页到磁盘,比例90%明显是太大了,因为假如内存很大,或者服务器压力很大,此时刷新90%那么多的数据,速度必然变慢,所以该参数的默认值修改为75了
InnoDB 1.2还引入了一个参数innodb_adaptive_flushing(自适应刷新),本来缓冲池中的脏页比例大于90%时才会刷新(现在是75%了),但是现在会通过计算重做日志(redo log)的速度来决定最合适的刷新脏页数量,当这个数量大于自适应刷新的配置时,也会进行一次刷新,可见脏页比例小于75%时,也会通过自适应刷新处理一定的脏页。
同时对于刷新脏页的操作,Master Thread分离到一个单独的线程Page Cleaner Thread(脏页清除线程)中进行,从而减轻了主线程的工作,进一步提高系统的并发性能。
《MySQL技术内幕:InnoDB存储引擎》连载 - 华章图书 - ITeye知识库频道
网友评论