InnoDB存储引擎及其特性

1.1线程

InnoDB存储引擎

InnoDB引擎是多线程模型,因此后台有很多线程:

  • Master Thread:非常核心的一个线程,主要将缓存池中的数据异步刷新到磁盘,包括脏页刷新,合并插入缓存(insert buffer),undo页的回收。
  • IO Thread:InnoDB使用大量AIO(Async io)来处理写请求,而IO Thread主要工作就是负责这些IO请求的回调,InnoDB1.0版本前有4个IO Thread,分别是write,read,insert buffer,log io thread,从1.0.x版本开始,read thread和write thread分别增大到4个,可以根据参数innodb_read_io_threads和innodb_write_io_threads来设置读写线程,并且读线程ID要小于写线程
  • Purge Thread:事务提交后,其所使用的undolog可能不再使用,因为需要Purge Thread回收已经使用的undo页,1.1版本前,purge仅在Master Thread中完成,1.1版本后,单独提出来Purge Thread
  • Page Cleaner thread:作用将脏页刷新放入单独线程中。

1.2内存

1.2.1缓冲池

InnoDB基于磁盘储存,将数据按照页的方式管理,必然cpu和磁盘速度之间会有差异,于是缓冲池技术就是应对这种情况而诞生的。
其实缓冲池就是一块内存区域,在数据库中进行读取页的操作,首先将磁盘读到的页存放到缓冲池中,下次再读取会判断该页是否在缓冲池,如果在就称该页在缓冲池被命中,否则读取磁盘。
对于数据库页的修改,首先修改缓冲池的页,再以一定的评率刷新到磁盘,这里是通过一种Checkpoint机制刷新到磁盘中。缓冲池配置通过innodb_buffer_pool_size来设置。


innodb内存数据对象

从 InnoDB 1.0.x 版本开始,允许有多个缓冲池实例。每个页根据哈希值平均分配到不同缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力

1.2.2.LRU List、Free List、Flush List

数据库中的缓冲池是通过 LRU ( Latest Recent U sed,最近最少使用〉 算法来进行管理的 。即最频繁使用的页在 LRU 列表的前端,而最少使用的页在 LRU 列表的尾端。当缓冲池不能存放新读取到的页时 ,将首先释放LRU 列表中尾端的页。

在 InnoDB 存储引擎中,缓冲池中页的大小默认为 16KB,同样使用 LRU 算法但是做了优化,LRU 列表中还加入了midpoint 位置。新读取到的页,虽然是最新访问的页,但并不是直接放入到 LRU 列表的首部,而是放入到 LRU 列表的midpoint
,默认值是5/8,参数 innodb_old_blocks_pct控制,midpoint 之后的列表称为 old 列表,之前的列表称为 new 列表。

那为什么不采用朴素的 LRU 算法,直接将读取的页放入到 LRU 列表的首部呢?这 是因为若直接将读取到的页放入到 LRU 的首部,那么某些 SQL 操作可能会使缓冲池中 的页被刷新出,从而影响缓冲池 的效率。常见的这类操作为索引或数据的扫描操作。这 类操作需要访问表中的许多页,甚至是全部的页,而这些页通常来说又仅在这次查询操 作中需要,并不是活跃的热点数据。如果页被放人 LRU 列表的首部,那么非常可能将所需要的热点数据页从 LRU 列表中移除,而在下一次需要读取该页时,InnoDB 存储引擎需要再次访问磁盘。

为了解决这个 问题,lnnoDB 存储引擎引人了另一个参数来进一步管理 LRU 列表, 这个参数是 innodb_old_blocks_time ,用于表示页读取到 mid 位置后需要等待多久才会被 加人到 LRU 列表的热端。

需要注意的是因为缓冲池中的页还可能会被分配给自适应哈希索引、Lock 信息、Insert Buffer 等页,而这部分页不需要 LRU 算法进行维护,因此不存在于 LRU 列表中。可以结合上面的图查看。
前面提到的缓冲池命中率也就是Buffer pool hit rate,如果这个值太低,一般都是90%以上,就需要考虑下是否是全表扫描引起的LRU列表被污染的问题,比如唯一索引修改为普通索引,导致sql语句走了全表扫描。

innodb从1.0.x版本开始支持压缩页的功能,即将原本16KB的页压缩为1,2,4,8KB,对于非16KB的页,是通过unzip_LRU列表管理,要注意的是LRU中的页包含了unzip_LRU列表中页。

在 LRU 列表中的页被修改后,称该页为脏页 ( dirty page ),即缓冲池中的页和磁盘 上的页的数据产生了不一致。这时数据库会通过 CHECKPOINT 机制将脏页刷新回磁盘, 而 Flush 列表中的页即为脏页列表。需要注意的是,脏页既存在于 LRU 列表中,也存在于 Flush 列表中。LRU 列表用来管理缓冲池中页的可用性,Flush 列表用来管理将页刷新回磁盘,二者互不影响。

1.2.3redo log buffer(重做日志缓冲)

InnoDB 存储引擎的内存区域除了有缓冲池外,还有redo log buffer。lnnoDB 存储引擎首先将重做日志信息先放人到这个缓冲区,然后刷新到重做日志文件。重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可 。该值可由配置参数 innodb_log_buffer_size 控制。
redo log buffer会在下面三种情况被刷新到磁盘的重做日志文件中:

  • Master Thread 每一秒将重做日志缓冲刷新到重做日志文件
  • 每个事务提交时会将重做日志缓冲刷新到重做日志文件
  • 当重做日志缓冲池剩余空间小于 1/2 时,重做日志缓冲刷新到重做日志文件

1.3Checkpoint

倘若每次一个页发生变化 ,就将新页的版本刷新到 磁盘,那么这个开销是非常大 的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差 。同时,如果在从缓冲池将页的新版本刷新到磁盘时发生了宕机 ,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了Write Ahead Log 策略,即当事务提交时,先写重做日志 ,再修改页。当由于发生者机而导致数据丢失时,通过重做日志来完成数据的恢复。这也是事务 ACID 中D ( Durability 持久性〉 的要求。
我们需要明白如下几点:

  • 缓冲池不可能缓存数据库中所有数据,也许数据库在重启后的最开始一段时间能做到。
  • 重做日志不可以无限增大
    因此 Checkpoint (检查点〉 技术的目的是解决以下几个问题
  • 缩短数据库的恢复时间
  • 缓冲池不够用时,将脏页刷新到磁盘
  • 重做日志不可用时,刷新脏页

当数据库发生宕机时,数据库不需要重做所有的日志,因为 Checkpoint 之前的页都已经刷新回磁盘。故数据库只需对 Checkpoint 后的重做日志进行恢复。这样就大大缩短了恢复的时间。

此外,当缓冲池不够用时,根据 LRU 算法会溢出最近最少使用的页,若此页为脏 页,那么需要强制执行 Checkpoint ,将脏页也就是页的新版本刷回磁盘。

重做日志出现不可用的情况是因为当前事务数据库系统对重做日志的设计都是循 环使用的,并不是让其无限增大的,这从成本及管理上都是比较困难的。重做日志可以被重用的部分是指这些重做日志已经不再需要,即当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。若此时重做日志还需要使用,那么必须强制产生 Checkpoint ,将缓冲池中的页至少刷新到当前重做日志的位置。
需要注意的关于redo log有这样几点

  • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是),这也是为什么很大的事务的提交时间也是很短的。
  • 合并插入缓冲(可能)(这也导致了mysql server层面的binlog机制和这个redo log可能数据会对不上,即使是二阶段提交协议下)

2.innodb更多特性

  • Change Buffer(之前是Insert Buffer)
  • 两次写 ( Double Write )
  • 自适应哈希索引 ( Adaptive Hash Index )
  • 异步 IO (Async IO)
  • 刷新邻接页 ( Flush Neighbor Page)

2.1Change Buffer

lnnoDB 存储引擎可以对DML 操作:INSERT、DELETE 、UPDATE 都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge Buffer。
关于Delete Buffer、Purge Buffer这里提一句:当你在删除某条记录时,分为两步:第一步是Delete Buffer将记录标记为删除,Purge Buffer将记录真正删除。
Change Buffer 是干什么的了?Change Buffer是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。
那么还有一个问题:为何只适合非唯一普通索引?
很简单,如果索引设置了唯一(unique)属性,在进行修改操作时,InnoDB必须进行唯一性检查。也就是说,索引页即使不在缓冲池,磁盘上的页读取无法避免。
所以不适合开启innodb的写缓冲机制的场景是:

  • 数据库都是唯一索引
  • 写入一个数据,需要马上读取它
    相反适合开启Change buffer的场景是:
  • 数据库大部分索引是非唯一索引
  • 写多读少(比如账单流水,日志记录)

2.2Double Write

关于IO的最小单位:

  • 数据库IO的最小单位是16K(MySQL默认,oracle是8K)
  • 文件系统IO的最小单位是4K(也有1K的)
  • 盘IO的最小单位是512字节
    doublewrite:两次写提高innodb的可靠性,用来解决部分写失败(partial page write页断裂)。
    比如这样的场景:当发生数据库着机时,可能lnnoDB 存储引擎正在写人某个页到表中,而这个页只写了一部分,比如 16KB 的页,只写了前 4KB ,之后就发生了宕机,这种情况被称为部分写失效 (partial page write )(redo log重做日志记录的是对页的物理修改,如果页本身已经损坏,重做日志也无能为力)

doublewrite由两部分组成,一部分为内存中的doublewrite buffer,其大小为2MB,另一部分是磁盘上共享表空间(ibdata x)中连续的128个页,即2个区(extent),大小也是2M。

  • 当一系列机制触发数据缓冲池中的脏页刷新时,并不直接写入磁盘数据文件中,而是先拷贝至内存中的doublewrite buffer中;
  • 接着从两次写缓冲区分两次写入磁盘共享表空间中(连续存储,顺序写,性能很高),每次写1MB;
  • 待第二步完成后,再将doublewrite buffer中的脏页数据写入实际的各个表空间文件(离散写);(脏页数据固化后,即进行标记对应doublewrite数据可覆盖)


    double write工作流程

如果操作系统在将页写入磁盘的过程中发生崩溃,在恢复过程中,innodb存储引擎可以从共享表空间的doublewrite中找到该页的一个最近的副本,将其复制到表空间文件,再应用redo log,就完成了恢复过程。因为有副本所以也不担心表空间中数据页是否损坏。(redolog写入的单位就是512字节,也就是磁盘IO的最小单位,所以无所谓数据损坏,所以不需要doublewrite的支持)

2.3自适应哈希索引

哈希是一种非常快的查找方法,在一般情况下这种查找的时 间复杂度为 0(1), 一般仅需要一次查找就能定位数据 。而 B+ 树的查找次数,取决于 B+ 树的高度,在生产环境中,B+树的高度一般为 3、4 层,故需要 3、4 次的查询。
InnoDB 存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带 来速度提升 ,则建立哈希索引,称之为自适应哈希索引 (Adaptive Hash Index, AHi)。

2.4异步IO

如其明,这里不多说。

2.5刷新邻接页

innoDB 存储引擎还提供了Flush Neighbor Page (刷新邻接页〉 的特性。其工作原理刷新一个脏页时,InnoDB 存储引擎会检测该页所在区的所有页,如果是脏页,那么一起进行刷新。这种做法在机械硬盘时代是由显著的优势的。但也会出现下面问题:

  • 会将不怎么脏的页频繁写入,该页又会很快变成脏页
  • 固态硬盘有着很高的io能力,这个参数意义不大

本文主要是自己看书和专栏文章来写,其实更多算是一种笔记。以后方便记忆和复习。

你可能感兴趣的:(InnoDB存储引擎及其特性)