InnoDB存储引擎详解

InnoDB存储引擎其特点是:

行锁设计,支持MVCC,支持外键,提供一致性非锁定读

目前InnoDB是数据库默认的存储引擎,其广泛应用于OLTP应用—支持事务,完全支持ACID特性。

InnoDB体系架构:

image.png

InnoDBc=存储引擎有很多内存块,可以认为这些内存块组成了一个大的内存池,负责工作如下:

1 维护所有进程/线程需要访问的多个内部数据结构

2 缓存磁盘上的数据结构方便快速地读取,同时在对磁盘文件的数据修改之前在这里做缓存。

3 重做日志缓存等

。。。。。。。。。。

后台线程主要负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最新的数据。此外将已经修改的数据文件刷新到磁盘,同时保证在数据库发生异常的情况下InnoDB能够恢复正常运行状态。

后台线程

InnoDB存储引擎是多线程的模型,因此后台有多个不同的线程,负责处理不同的任务

  • Master Thread :主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新,合并插入缓冲,UNDO页的回收等

  • IO Thread:InnoDB存储引擎大量使用AIO来处理IO请求,这样可以极大的提高数据库的性能。IO Thread主要工作就是负责这些IO请求的回调处理,write,read,insert buffer,log IO Thread等线程

    show variables like '%in_threads%'

    show engine innodb status

  • Purge Thread :事务被提交后,其所使用的undolog可能不再需要,因此需要Purge Thread来回收已经使用并分配的undo页

内存

  • 缓冲池:

    InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以视为基于磁盘的数据库系统。由于CPU速度和磁盘速度之间的鸿沟,需要使用缓冲池技术来提高数据库的整体性能。

    缓冲池简单来说就是一块内存区域,通过内存的速度来磁盘速度对数据库性能的影响。在数据库中进行页的读取操作,首先将从磁盘读到页存放的缓冲池中,这个过程叫做将页“FIX”在缓冲池中,下次读取相同页的时候首先读取缓冲池,未命中再读取磁盘中的页。

    对于数据库页的修改,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘,注意页从缓冲池刷新到磁盘并不是修改的时候就触发,而是通过一种称为CheckPoint的机制刷新回磁盘。所以缓冲池的大小直接影响了数据库的性能。

    具体来说缓冲池中缓存的数据页类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典信息等。不能简单认为,缓冲池中只是缓冲索引页和数据页,他们只是占很大一部分而已

    下图是InnoDB存储引擎的内存情况:


    image.png

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

    show engine innodb status

    select * from innodb_buffer_pool_stats

    以上命令可以查看数据库缓冲池的情况

  • LRU list,Free List,Flush List

    前面讲解了数据库缓冲池,其中存放了很多不同种类的页,InnoDB是如何对这么大的内存进行管理的?以上这些列表就是进行管理的关键!

    LRU列表

    通常来说数据库缓冲池中的页是通过LRU(Latest Recent Used)算法进行管理的,最频繁使用的页在LRU的前端,最少使用的.当缓冲池中不能存放新读取的页时,将首先释放LRU列表尾部的页。

    注意缓冲池中页的大小默认时16kB,InnoDB对LRU算法进行了优化,InnoDB中有一个midpoint位置,最新读取的页不是放在LRU列表的最前端而是放在midpoint位置,midpoint可以通过配置文件参数进行自定义默认是默认列表尾部的37%位置,midpoint前面的部分为new列表,后面的微old列表。如果不优化的话如果遇到索引或数据的扫描操作的时候会加载大量的数据,导致将缓冲池中的数据页刷新出到磁盘中,关键这些扫描操作很多时候是本次查询需要,并不是活跃的热点数据。

    InnoDB存储引擎有一个参数innodb_old_block_time参数表示mid位置后需要等待多久才会被加入到LRU列表的热端

    Free List

    LRU列表被使用来管理已经读取的页,数据库启动的时候这个列表是空的,这时页都存放在Free列表中。需要在缓冲池中进行分页的时候首先查看Free中的可用空闲页,若有则将该页从Free列表删除放入LRU列表中。否则根据LRU算法淘汰页;;;注意LRU中页+Free中的页大小小于缓冲池,因为缓冲池中还有自适应哈希,Lock信息,Insert Buffer等页,而这部分是不需要LRU算法维护的,所以不会出现在LRU列表中。

    Buffer pool hit rate表示缓冲池的命中率,一般应该在95以上,如果小于95%就应该留意是不是全表扫描引起了LRU列表被污染。

    select * from INNODB_BUFFER_PAGE_LRU命令观察每个LRU列表中每个页的具体信息。InnoDB1.0版本开始支持页的压缩,所以LRU列表分为两种,未压缩的页使用LRU列表进行管理,压缩的页使用unzip_LRU列表进行管理,LRU列表中的页包含unzip_LRU列表中的页。

    unzip_LRU对不同压缩大小的页分开管理,如果需要从缓冲池中申请4kB的空间,首先检查4kB部分的unzip_LRU,没有再检查8kB部分的—如果有分为两块4kB的存到4kB部分,若还是没有则在LRU中获取16kB的页将页分为2个4kB的和1个8kB的分别放置到不同部位。

    Flush List

    LRU列表中的页被修改后被称为脏页,就是缓冲池中的页和磁盘中的页数据不一致。这个时候需要数据库通过checkPoint机制将脏页刷新回磁盘,Flush列表中的页就是脏页,注意脏页既存在于LRU列表中又存在于Flush列表中。LRU列表用来管理页的可用性,Flush用来管理脏页

  • 重做日志缓冲

    InnoDB先将重做日志缓冲到缓冲区,然后再按照一定频率(每秒)刷新到重做日志文件,所以重做日志缓冲不需要太大,只要保证1秒的事务量可以存下就好了。

    一般8MB的重做日志缓冲就足够满足大部分应用,重做日志会在下面几个情况下写入重做日志文件,

    1 Master Thread每秒将重做日志缓冲刷新到磁盘的重做日志文件

    2 每个事务提交时会将重做日志缓冲到重做日志文件

    3 当重做日志缓冲池空间小于1/2时

CheckPoint技术:

每次DML操作的时候就会导致脏页,但不是每次DML操作导致脏页就将脏页写会磁盘,如果每次脏页都写回磁盘,如果热点数据集中在几页中,数据库性能就会很差。并且如果数据从缓冲池中刷新到内存的时候宕机,那数据就不能恢复了;所以目前数据库采用的都是Write Ahead log策略,既当事务提交前,先写重做日志再修改页。当由于宕机导致数据丢失时,通过重做日志来完成数据恢复,这也是ACID中D(Durability持久性)

太频繁的刷新导致数据库性能很差,长时间不刷新即使可行但是服务器宕机需要恢复的时候需要很长的时间。

checkPoint技术就是解决这几个问题的:

1 缩短数据库的恢复时间

2 缓冲池不够用时,将脏页刷新到磁盘

3 重做日志不可用时,刷新脏页

当数据库发生宕机时,数据库不需要重做所有日志,因为checkPoint之前的页都已经刷新回磁盘了;此外如果由于缓冲池不够用的时候LRU的前端页会溢出,如果这些页是脏页那么需要强制执行CheckPoint,将脏页刷新回磁盘。

InnoDB存储引擎有两种类型的checkPoint

Sharp CheckPoint—数据库关闭的时候将脏页刷新回磁盘

Fuzzy CheckPoint---将脏页部分刷新回磁盘

Fuzzy CheckPoint-又细分为:

  1. Master Thread CheckPoint

    以每秒或10秒的速度从缓冲池中刷新脏页到磁盘,这个过程是异步的。

  2. FLUSH_LRU_LIST CheckPoint

    InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用,,如果没有就会移除LRU尾部非活跃页,如果这些页有脏页就要进行CheckPoint

  3. Async/Sync Flush CheckPoint

    重做日志文件不可用的时候,需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的—其是为了保证重做日志文件的循环使用的可用性

  4. Dirty Page too much CheckPoint

    脏页数量太多强制进行CheckPoint

Master Thread具有最高的线程优先级别,内部有多个循环组成,主循环,后台循环,刷新循环,暂停循环,会根据数据库状态在各个循环之间切换。

主循环分为1秒的和10秒的

1秒的操作包括:

1 日志缓冲刷新到磁盘,即使这个事务还没有提交

2 合并插入缓冲(可能)

3 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)

4 如果当前没有用户活动,切换到后台循环(可能)

即使某个事务还没有提交,重做日志也会被每秒刷新到重做日志文件,这一点很重要—这就是为何再大的事务提交时间也很短。

合并插入缓存并不是每次都发生,如果当前一秒的IO次数小于5次,数据库会判定压力很小就会进行合并插入缓冲,

至多刷新100个InnoDB的缓冲池中的脏页到磁盘,这个也不是每秒发生,而是检查当前缓冲池中脏页的比例是否超过了设置的值来确定的。

10秒操作包括:

1 刷新100个脏页到磁盘

2 合并至少5个插入缓冲(总是)

3 将日志缓冲刷新到磁盘(总是)

4 删除无用的undo页(总是)

5 刷新100个或者10个脏页到磁盘(总是)

InnoDB关键特性:

插入缓冲,两次写,自适应哈希,异步IO,刷新临接页

  1. 插入缓冲

    不要以为插入缓冲是缓冲池的一个组成部分,缓冲池中确实有自己的Insert Buffer但是这里我们讨论的Insert Buffer页时物理页的一个组成部分。

    在InnoDB存储引擎中,主键是行唯一的标识符。通常程序中行记录的插入顺序是按照主键递增的顺序进行插入的,因此插入聚集索引一般是顺序的,不需要磁盘的随机读取。页中的记录按照主键顺序进行存放,所以不需要随机读取另一个页中的记录。注意不是所有主键插入都是顺序的,如果使用想UUID这样的主键就不是递增的,所以插入也是和辅助索引一样是随机的----注意即使主键是自增类型的,但是插入的是指定的值,那么同样可能导致插入并非连续的情况。

    每个表只有一个聚集索引—主键索引,剩下的都是辅助索引;对于表A中主键索引a和辅助索引b,a的插入是顺序的,其页子节点存储真实数据,按照主键顺序存放;但是插入辅助索引b的时候,b的页子节点插入不再是顺序的,这时就需要离散的访问非聚集索引页,由于随机读取的存在导致插入性能下降,当然这不是b索引的错,而是索引使用的B+树数据结构的特性导致的非聚集索引插入的离散性。

    需要注意的是,在某些情况下,辅助索引的插入依然是顺序的,或者说比较顺序,入按照操作时间插入,操作时间是辅助索引,且每次操作时间是递增的,插入就是按照时间递增插入的。

    InnoDB存储引擎开创性的设计了Insert Buffer,对于非聚集索引的插入和更新操作不是每次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入,若不在则先放入Insert Buffer对象中,好似欺骗数据库这个非聚集索引已经插入到叶子结点,而实际没有,只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge操作。这样就将多个插入合并到一个操作中。

    然而Insert Buffer的使用需要同时满足以下两个条件:

    索引是辅助索引,索引不是唯一的------如果辅助索引是唯一的,那么每次插入的时候都要反查数据库校验唯一性,这样又离散的读了,没有解决离散读的问题。

    不过在应对写密集形的应用的时候Insert Buffer会过多的占用缓冲池内存。

    Insert Buffer Delete Buffer ,Purge Buffer

    Insert Buffer的实现细节讲解

    数据结构

    数据结构使用的是B+树,之前版本是每个表一个B+树,后面版本全局只有一棵B+树,这棵树存在共享表空间中,所以通过ibd文件恢复后还需要进行REPAIR TABLE操作来重建表上所有的辅助索引。

    B+树中非页子节点存放的是查询的search key(键值),构造如下:9个字节分别为4,1,4


    image.png

    space表示代插入记录所在表的表空间ID,InnoDB中每个表都有一个表空间ID,这个ID定位到具体哪张表

    marker 兼容老版本的Insert Buffer

    offset表示页所在的偏移量

    当一个辅助索引要插入到页时,如果这个页不在缓冲池中,那么InnoDB首先构造一个search key,接下来查询Insert Buffer这颗B+树,然后再将这条查询插入到树中。

    叶子结点结构如下 4,1,4,4


    image.png
image.png
IBUF_REC_OFFSET_COUNT:保存两个字节的整数,用来排序每个记录进入Insert Buffer的顺序,Change Buffer的值页记录在这里,这样通过顺序回放才能得到记录的正确值。

从Insert Buffer页子节点的第五列开始存储的真实记录的各个字段。

**Merge Insert Buffer**

何时将Insert Buffer这个B+树和辅助索引进行Merge ?一般在以下情况下进行Merge

1 辅助索引页被读取到缓冲池中

2 Insert Buffer Bitmap页追踪到该辅助索引页以无可用空间时

第一种情况如:执行查询语句时(相关辅助索引会被加载到缓冲池中),检查Insert Buffer Bitmap页,确认该辅助索引页是否有记录存放在Insert Buffer B+树中,若有,则将Insert Buffer中该页的数据插入辅助索引

第二种情况 Insert Buffer BitMap页用来追踪每个辅助索引页的可用空间,并至少有1/32页的空间,若插入辅助索引记录检测到插入记录后小于,会强制进行一次合并,将Insert Buffer B+树中该页的记录及待插入的记录插入到辅助索引页中。

最后就是 Master Thread每秒或10秒进行一次Merge Insert Buffer操作,每次进行Merge的页数量不确定。

注意Insert BufferB+树都是根据space ,offset排好序的,所以可以根据这个顺序进行页的选择。
  1. 两次写

    如果说Insert Buffer给数据库带来的是性能的提升,那么 double write带来的就是数据页的可靠性

    当发生宕机时,可能InnoDB存储引擎正在写入某个页到列表中,而这个页只写了一部分,这种情况被称为部分写失败;可能有读者会说通过重做日志进行恢复,但是读者必须知道,重做日志中记录的是对页的物理操作,比如偏移量800,写‘aaaa’记录。如果这个页本身已经发生损坏,对其再进行重做是没有意义的。

    所以在应用重做日志前,用户需要一个页的副本,当写入失效发生时,先通过页的副本还原该页,再进行重做,

    Double Write体系结构如下:

image.png
由两部分组成,一部分是内存中的double write buffer大小为2M,另一部分为物理磁盘上共享表空间中连续的128个页,即2个区。在对缓冲池的脏页进行刷新的时候,并不是直接写磁盘,而是将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer分两次每次1M的顺序地写入共享表空间的物理磁盘上,然后再调用fsyn函数,同步磁盘,避免缓冲写带来的问题,在这个过程中,因为共享表空间中double write 页是连续的,因此这个过程是连续写,开销不是很大;完成写入后再将double write buffer中的页写入各个表空间文件中,最后的写入是离散的。

如果操作系统将页的数据写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的doublewrite中找到该页的副本将其复制到表空间文件中,再应用重做日志。
  1. 自适应哈希索引 AHI(Adaptive Hash Index)

    哈希查找时间复杂度为O(1),而B+树的高度一般3-4层所以需要3-4次查询

    InnoDB存储引擎会监控对表上各个索引页的查询,如果观察到建立哈希索引可以带来速度提升,则建立哈希索引

    AHI是通过缓冲池的B+树页构造而来,因此建立速度很快,而且不需要对整张表建立哈希索引。InnoDB会根据访问频率和模式来自动为 某些热点页建立哈希索引。

  2. 异步IO

    AIO的一个优势就是可以进行IO Merge操作,也就是将多个IO合并为一个。

  3. 刷新临接页

    当刷新一个脏页会检测页所在区的其他页,如果也是脏的就一起刷新

你可能感兴趣的:(InnoDB存储引擎详解)