《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

目录

四 InnoDB存储引擎

(一)InnoDB体系架构

1 后台线程

2 内存

(二)CheckPoint技术

(三)Master Thread工作方式

1 1.0.x之前版本

2 1.2.x之前版本

3 1.2.x版本

(三)InnoDB关键特性

1 插入缓冲

2 两次写

3 自适应哈希索引

4 异步IO

5 刷新邻接页


四 InnoDB存储引擎

InnoDB存储引擎是事务安全的MySql存储引擎,从MySql5.5版本开始成为默认的表存储引擎,是第一个完整支持ACID事务的MySql存储引擎,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读。

(一)InnoDB体系架构

InnoDB存储引擎是一个多线程的的模型,由多个后台线程和内存池共同组成,后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件中,并保证数据库在发生异常情况时能够恢复到正常状态。

《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

 

1 后台线程

(1)Master Thread

Master Thread是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、UNDO页回收等。

(2)IO Thread

InnoDB中大量使用了AIO(Async IO)来处理写IO请求,极大提高了数据库的性能。IO Thread的工作主要是负责这些IO请求的回调处理。

InnoDB 1.0版本之前共有4个IO Thread,分别是write、read、insert buffer和log IO thread。

在1.0.x 版本开始,read thread和write thread分别增大到了4个。

《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

可以通过innodb_read_io_threads和innodb_write_io_threads参数可设置读写线程数。

(3)Purge Thread

事务提交后,UNDO日志将不再需要,因此需要Purge Thread来回收已经使用并分配的UNDO页,在InnoDB1.1 版本之前,purge操作仅在InnoDB存储引擎的Master Thread中完成,从1.1 版本开始,purge操作在单独的Purge Thrad中进行。

从InnoDB1.2 版本开始,支持多个Purge Thread同时工作。

(4)Page Cleaner Thread

Page Cleaner Thread是在InnoDB1.2.x 版本中引入的,作用是将脏页刷新的操作放到单独的线程中完成。

2 内存

(1)缓冲池

InnoDB存储引擎是基于磁盘存储的,并按照页的方式管理记录。为了缓解磁盘速度和CPU速度间的鸿沟,通过缓冲池进行弥补。

对数据库页读取的操作,首先将从磁盘读到的页放在缓冲池中,这个过程称为将页FIX在缓冲池中,再读取相同页时,首先在缓冲池中读取,若存在则缓存命中,否则到磁盘读取。

对数据库页的修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上,这里的刷新操作不是发生在每次更新时,而是通过Checkpoint机制刷新到磁盘。

可以通过innodb_buffer_pool_size参数设置缓冲池的大小,单位为字节。

InnoDB的内存数据对象如下图所示:

《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

从图中可以看到,缓冲池中缓存的数据类型有:数据页、索引页、插入缓存、自适应哈希索引、锁信息、数据字典信息。

从1.0.x 版本开始,允许有多个缓冲池实例,每个页根据哈希值平均分配到不同缓冲池实例中,可以通过参数innodb_buffer_pool_instances配置。

(2)LRU List

通常,数据库中的缓冲池是通过LRU算法来管理的。

在InnoDB中,缓冲池中页的大小默认为16KB,同样使用LRU算法进行管理,不同的是InnoDB对LRU算法做了一些优化。

在InnoDB中,LRU列表还加入了midpoint位置,当读取到新的页时,虽然是最新访问的,但并不直接放入LRU列表的首部,而是放入到LRU列表的midpoint位置,在默认配置下,该位置在LRU列表的5/8处,可以通过innodb_old_blocks_pct参数配置。

《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

如上图的配置,表示新读取的页插入到LRU列表的37%的位置。在InnoDB中,把midpoint之后的列表称为old列表,之前的称为new列表。

为了防止进行锁表扫描时导致缓冲池中的热点页被刷新出,InnoDB引入了innodb_old_blocks_time参数进一步管理LRU列表,这个参数表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端。

(3)Free List

LRU列表用来管理已经读取的页,但数据库刚启动时,LRU列表是空的,这是页都存放在Free列表中,当需要从缓冲池中分页时,首先从Free列表查找是否有可用的空闲页,若有则将该页从Free列表删除,加入LRU列表中。否则,根据LRU算法,淘汰LRU列表末尾的页,将该内存空间分配给新的页。

InnoDB自1.0.x 版本开始支持压缩页的功能,可将原本16KB的页压缩为1KB、2KB、4KB和8KB的页,对于这些非16KB的页,是由unzip_LRU列表管理的。

《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

(4)Flush List

在LRU列表中的页被修改后,称为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据不一致,这是数据库会通过CheckPoint机制将脏页刷新回磁盘,而Flush列表中的页即为脏页,需要注意,脏页既位于Flush列表,又位于LRU列表。

(5)重做日志缓存

InnoDB的内存区除了有缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB首先将重做日志放到这个缓冲区,然后按一定的频率将其刷新到磁盘重做日志文件,重做日志缓存默认大小为8MB,可通过innodb_log_buffer_size参数配置。

通常,8MB的重做日志缓存可以满足大部分应用。在以下三种情况下,会将重做日志缓存刷新到磁盘重做日志文件中:

  1. Master Thread每一秒刷新。
  2. 每个事务提交时刷新。
  3. 重做日志缓存剩余空间小于1/2时刷新。

(6)额外的内存池

在InnoDB存储引擎中,内存是通过堆的方式管理的,一些数据结构在分配内存时会在额外的内存区域分配,比如缓冲池控制对象(记录LRU、锁、等待等信息),当额外内存区域不够时,会从缓冲池中申请内存。

(二)CheckPoint技术

用户对数据库的操作首先都是在缓冲池中完成的,在一个页称为脏页后,需要将页数据刷新到磁盘数据文件中,这就存在两个问题:

1. 如果只要页发生变化就进行刷新操作,那么性能会很差。

2. 刷新操作过程中发生宕机,数据会丢失。

为了避免数据丢失,当前的数据库系统普遍采用Write Ahead Log策略,即事务提交时,先写重做日志,再修改页。

这样一来,脏页的刷新就需要一定的机制来控制,否则当数据库宕机后,需要根据大量的重做日志进行恢复,因此CheckPoint技术的目的是解决如下问题:

  1. 缩短数据库的恢复时间。
  2. 缓冲池不够时,刷新脏页到磁盘。
  3. 重做日志不可用时,刷新脏页到磁盘,也就是目前事务型数据库的重做日志是重用的,当想要重用一部分重做日志时,如果这些日志已经无效,则直接重用,否则需要先将数据刷新至重做日志的版本,再重用。

在InnoDB中,有两种CheckPoint:Sharp CheckPoint和Fuzzy CheckPoint。

Sharp CheckPoint会将所有脏页都刷新回磁盘,发生在数据库关闭时。

Fuzzy CheckPoint只刷新一部分脏页,发生在数据库运行时,这种机制会有以下几种情况:

  1. Master Thread CheckPoint:每秒或每十秒刷新一部分脏页到磁盘上,这个过程是一步的,不会阻塞查询线程。
  2. FLUSH_LRU_LIST CheckPoint:InnoDB需要保证LRU列表中有一定数量的空闲页可用,当空闲页数量不够时,会将LRU列表尾部的页移除,并将被移除的页中的脏页刷新到磁盘上。在1.1.x版本之前,这个操作是由用户查询线程完成的,自InnoDB1.2.x开始放在了Page Cleaner Thread中,且可通过innodb_lru_scan_depth参数控制空闲页数量。
  3. Asunc/Sync Flush CheckPoint:重做日志不可用时,需要强制将一些Flush列表中的脏页刷新到磁盘。
  4. Dirty Page too much CheckPoint:当脏页数量太多时,将一部分脏页刷新到磁盘,可以通过innodb_max_dirty_pages_pct参数设置脏页比例。

(三)Master Thread工作方式

Master Thread具有最高的线程优先级,其内部包括main loop、background loop、flush loop和suspend loop,Master Thread根据数据库的运行状态在各循环中切换。

1 1.0.x之前版本

1 main loop

主循环中包括两大部分的操作:每秒的操作和每十秒的操作。

每秒的操作:

  1. 日志缓冲刷新到磁盘,即使这个事务还没提交(总是)
  2. 合并插入缓冲(可能):判断若前一秒的IO次数小于5次,则执行操作
  3. 至多刷新100个脏页到磁盘(可能):脏页比例如果已经超过设置的比例,则执行操作
  4. 如果没有用户活动,切换到后台循环

每十秒的操作:

  1. 刷新100个脏页到磁盘(可能):判断若前10秒的IO次数小于200次,则执行操作
  2. 合并至多5个插入缓存(总是)
  3. 日志缓冲刷新到磁盘(总是)
  4. 删除无用的Undo页(总是):每次最多尝试回收20个Undo页
  5. 刷新100个或10个脏页到磁盘(总是):若脏页比例超过70%,则刷新100个脏页,否则刷新10个脏页。

2 background loop

删除无用Undo页(总是)

合并20个插入缓冲(总是)

跳回主循环(总是)

不断刷新100个页直到符合条件(可能,跳转到flush loop)

3 flush loop

刷新页到磁盘,如果flush loop没有什么可做的了,则却环岛suspend loop,将Master Thread挂起。

2 1.2.x之前版本

1.0.x 版本之前,每次至多刷新100个脏页到磁盘,合并20个插入缓存,,从1.0.x版本开始加入了innodb_io_capacity配置刷新脏页的数量。

脏页的默认比例由90%降到75%。

引入innodb_adaptive_flushing(自适应刷新),用于配置每秒刷新脏页的数量。

1.0.x之前,最多回收20个Undo页,自1.0.x引入innodb_purge_batch_size配置每次回收的Undo页数量。

3 1.2.x版本

将脏页刷新的操作分离到单独的Page Cleaner Thread中。

(三)InnoDB关键特性

InnoDB存储引擎的关键特性包括:插入缓冲(Insert Buffer)、两次写(Double Write)、自适应哈希索引(Adaptive Hash Index)、异步IO(Async IO)、刷新邻接页(Flush Neighbor Page)。

1 插入缓冲

(1Insert Buffer

我们知道在插入一条数据的同时还需要插入索引,Insert Buffer就是针对索引页设计的一种优化,插入缓存生效的前提是:

  1. 非聚集索引
  2. 非唯一索引

在InnoDB中,主键是行唯一的标识,主键列默认是聚集索引(数据行的物理顺序与索引列的逻辑顺序相同),一定是唯一的,在索引页插入一个聚集索引时,一定需要读取其他索引页判断是否唯一,这是主键约束必须付出的代价,不在Insert Buffer的优化范围内。

但我们需要关注一点:通常我们是不会修改主键的,而其他数据列可能会频繁的修改。

在一张表中,我们可能在除主键列以外的其他数据列建立多个非聚集、非唯一索引,那么我们在插入大量数据时就需要插入大量的非聚集索引,Insert Buffer提供了一种优化方式:对于每一次的插入不是直接写到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在则直接插入;若不在,则先放到Insert Buffer中,再按照一定的频率将索引数据合并到索引页中,这样一来,Insert Buffer中的插入操作通常能够合并到一个操作中(因为在一个索引页中),这样就大大提高了非聚集索引的插入性能。

(2Change Buffer

InnoDB自1.0.x版本起开始引入了Change Buffer,支持对INSERT、DELETE、UPDATE都进行缓冲,分别对应Insert Buffer、Delete Buffer和Purge Buffer。

(3Merge Insert Buffer

Insert/Change Buffer实际上也是一棵B+树,我们将Insert/Change Buffer中的索引数据合并到相应的索引页中的操作称为Merge。

当以下情况发生时,会触发Merge操作:

  1. 索引页被读到缓冲池时,会检查是否有该索引页的记录存放于Insert Buffer中,若有则进行Merge。
  2. 当索引记录插入Insert Buffer后发现Insert Buffer可用空间小于1/32页,则读取该索引页到缓冲池中,并触发Merge操作。
  3. Master Thread每一秒或十秒会进行Merge操作。

2 两次写

当InnoDB正在刷新某个脏页到磁盘中,发生了宕机,这种情况称为部分写失效,我们通常的想法是通过重做日志进行恢复,但重做日志中记录的是关于数据页的物理操作,如偏移量800,写入’aaaa’,那么如果在宕机时数据页本身发生了损坏,那么重做则是没有意义的。因此,在进行重做恢复前,要有一个页的副本,先对磁盘数据页进行还原,得到一个正确的版本,然后再进行重做。

double write的过程如下图所示:

《MySql技术内幕 InnoDb存储引擎》学习笔记【四 InnoDB存储引擎】

这里需要注意的是,既然我们已经有了共享表空间中正确的数据页,为什么在恢复时还需要重做日志呢?

这是因为共享表空间中记录的是一个时刻的正确状态,并不一定是当前最新的正确状态,重做日志文件的操作可能领先于double write区,因此恢复时仍然需要根据重做日志文件进行恢复。

3 自适应哈希索引

自适应哈希索引是指InnoDB会监控对表上各索引页的查询,如果观察到可以通过建立Hash索引提高速度,则自动根据访问的频率和模式为某些热点页建立Hash索引。

自适应哈希索引要求对某个页的连续访问模式必须是相同的,比如 where a = xxx,且满足以下要求:

  1. 以该模式访问了100次
  2. 页通过该模式访问了N次,N = 页中记录数 / 16

4 异步IO

当前数据库系统都采用Async IO的方式来处理IO操作。

AIO指的是发出一个IO请求后,不等待该请求完成则继续发送其他IO请求,当所有请求发送完成后,等待所有IO请求的完成。

AIO可以进行IO Merge操作,即将多个IO合并为一个IO,如分别读取多个相邻的16K数据页可以合并为一次读取N * 16K的数据页。

5 刷新邻接页

当刷新一个脏页时,InnoDB会检测该页所在区的所有页,如果是脏页则一起刷新,这样就可以将多个IO操作合并为一个IO操作,这个特性在传统机械硬盘上具有显著的优势,在固态硬盘上不建议使用,可以通过innodb_flush_neighbors参数配置。

 

特此声明:本系列博客为均为《MySql技术内幕 InnoDb存储引擎》读书笔记,存在错误还请指正

参考资料

《MySql技术内幕 InnoDb存储引擎》