1. InnoDB体系架构
1.1 后台线程
后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据;此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能够恢复到正常状态。
后台线程有三种:
- Master Thread(核心线程;主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性)
- IO Thread(负责IO请求的回调处理)
- Purge Thread(回收已经使用并分配的 undo 页)
1.2 内存
1.2.1 缓冲池
InnoDB存储引擎是基于磁盘存储的,可将其视为基于磁盘的数据库系统。
缓冲池就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。
1.2.2 LRU List、Free List、Flush List
InnoDB数据库中的缓冲池是通过LRU算法来进行管理的,但是对该算法做了优化,LRU列表中加入了 midpoint 位置。
midpoint 之后的列表称为 old 列表,之前的列表成为 new 列表,可以简单理解为 new 列表中的页都是最为活跃的热点数据,读取到的数据页都会先放到 midpoint 位置。
如果只使用传统的LRU算法,那么直接将读取到的页放入到LRU的首部后,可能会在尾部移除所需要的热点数据页。
InnoDB为了进一步优化LRU,引入了 innodb_old_blocks_time 参数进一步管理LRU列表,该参数表示页读取到 midpoint 位置后需要等待多久才会加入到 LRU 列表的热端。
当数据库刚启动时,页都存放在 free list 中。当数据库启动一段时间后,需要从缓冲池中分页时,首先从 free list 中查找是否有可用的空闲页,有就将该页从 free list 中删除,放入到 LRU list 中;若没有,则使用 LRU 算法。
在 LRU 列表中的页都修改后,该页被称为脏页,即缓冲池中的页和磁盘上的页的数据产生了不一致。然后数据库会 CHECKPOINT 机制将脏页刷新会磁盘,而 Flush 列表中的页即为脏页。需要注意的是,LRU 列表和 Flush 列表中都有脏页,LRU 列表用来管理缓冲池中页的可用性,Flush 列表用来管理将页刷新回磁盘,二者互不影响。
1.2.3 重做日志缓冲
重做日志在下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中:
- Master Thread 每过一秒
- 每个事务提交时
- 当重做日志缓冲池剩余空间小于 1/2 时
1.2.4 额外的内存池
在 InnoDB存储引擎中,对内存的管理是通过一种称为内存堆的方式进行的。
2. Checkpoint 技术
如果每次一个页发生变化,就将新页的数据刷新到磁盘,那么这个开销是非常大的;若热点数据集中在某几个页中,那么数据库的性能将会变得非常差;同时如果在从缓冲池将页的新版本刷新到磁盘是发生宕机,那么数据就不能恢复了。故而当前的事务数据库系统普遍采用了 write ahead log 策略,即事务提交时,先写重做日志,再修改页。
数据库中的缓冲池是不能缓存数据库中所有的数据的,重做日志也不能无限增大。Checkpoint 技术的目的就是为了解决下述几个问题:
- 缩短数据库的恢复时间
- 缓冲池不够用时,将脏页刷新到磁盘
- 重做日志不可用时,刷新脏页
在 InnoDB引擎中,有两种 Checkpoint:
- Sharp Checkpoint(发生在数据库关闭时刷新所有脏页)
- Fuzzy Checkpoint (数据库运行时刷新部分脏页)
3. InnoDB关键特性
InnoDB存储引擎的关键特性包括:
- 插入缓冲
- 两次写
- 自适应哈希索引
- 异步IO
- 刷新邻接页
3.1 插入缓冲
InnoDB存储引擎开创性地设计了插入缓冲,对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,直接插入;若不在,则先放入到一个 Insert Buffer 对象中。
使用插入缓冲需要同时满足以下两个条件:
- 索引是辅助索引
- 索引不是唯一的
随着InnoDB版本的更新,从1.0.x版本开始引入了 Change Buffer,可将其视为 Insert Buffer 的升级。
接下来看一下插入缓冲的内部实现。插入缓冲的数据结构是一棵 B+ 树 ,由叶节点和非叶节点组成,非叶节点存放的是查询的 search key,如下所示:
space:表示待插入记录所在表的表空间 id(占用4字节)marker:用于兼容老版本(占用1字节)offset:表示页所在的偏移量
对于插入到 Insert Buffer B+树叶子节点的记录,并不是直接将待插入的记录插入,而是需要根据如下的规则进行构造:
3.2 两次写
插入缓冲带给 InnoDB存储引擎的是性能上的提升,两次写带给 InnoDB存储引擎的是数据页的可靠性。
在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的 doublewrite buffer,之后再将其分两次,一次写到共享表空间,一次写到数据文件,而当数据文件损坏时,从共享表空间中取出写入的脏页进行恢复。
3.3 自适应哈希索引
InnoDB存储引擎会自动根据访问的频率和模式来自动低为某些热点页建立哈希索引。自适应哈希索引有一个要求,即对这个页的连续访问模式必须是一样的。
访问模式如下例:
- WHERE a = xxx
- WHERE a = xxx and b = xxx
如果交替使用上述两种访问模式,那么就不会使用自适应哈希索引。
3.4 异步IO
采用异步IO,可以提高磁盘操作性能。异步IO就是用户可以在发出一个请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有IO操作的完成。
异步IO还有一个优势就是可以对多个IO进行合并操作。如果异步IO判断到多个IO请求的页是连续时,那么就会合并成一个IO操作,而不需要使用多次操作。
3.5 刷新邻接页
当刷新一个脏页时,InnoDB存储引擎会检测该页所在区的所有页,如果是脏页,那么一起进行刷新。其实也就是通过异步IO将多个IO操作合并成一个IO操作。