MySQL技术内幕-InnoDB存储引擎-第二章、InnoDB存储引擎

文章目录

InnoDB存储引擎

InnoDB是事务安全的MySQL存储引擎,通常来说,它是OLTP(Online Transactional Processing,联机事务处理)应用中核心表的首选存储引擎。

一、InnoDB存储引擎概述

从MySQL5.5版本开始,InnoDB开始是默认的表存储引擎(之前的版本InnoDB存储引擎仅在Windows下为默认的存储引擎)。该存储引擎是第一个完整支持ACID事务的MySQL存储引擎(BDB是第一个支持事务的MySQL存储引擎,现在已经停止开发)。
特点:

  • 支持行锁
  • 支持MVCC
  • 支持外键
  • 提供一致性非锁定读

InnoDB上处理插入/更新操作的速度平均为800次/秒。这都证明了InnoDB是一个高性能、高可用、高可扩展的存储引擎。

二、InnoDB存储引擎的版本

InnoDB存储引擎包含于所有的MySQL数据库的二进制发行版本中。早期其版本随着MySQL数据库的更新而更新。从5.1版本开始,MySQL数据库允许存储引擎开发商以动态方式加载引擎,这样存储引擎的更新可以不受数据库版本的限制
所以在5.1中,可以支持两个版本的InnoDB,一个是静态编译的InnoDB版本,可以看成是老版本的InnoDB,另外一个是动态加载的版本,可以将其看成InnoDB1.0.x版本,官方称为InnoDB Plugin。
MySQL5.5版本中又将InnoDB的版本升级到了1.1.x。而后最近的MySQL5.6版本中,InnoDB的版本也随着升级为1.2.x版本。
在这里插入图片描述
由于不支持多回滚段,InnoDB Plugin支持的最大支持并发事务数量也被限制在了1023。

三、InnoDB体系架构

下图显示了InnoDB的存储引擎的体系结构,从图中可看出,InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:

  • 维护所有进程/线程需要访问的多个内部数据结构
  • 缓存磁盘上的数据,方便快速的读取,同时在对磁盘文件的数据修改之前在这里缓存。
  • 重做日志(redo log)缓冲。

  • 在这里插入图片描述

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

1、后台线程

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

Master Thread(主要负责将缓存池数据刷新到磁盘)

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

IO Thread(主要负责IO请求的回调处理)

对于回调函数的理解
在InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大提高数据库的性能。而IO Thread的工作主要是负责这些IO请求的回调处理。

在InnoDB1.0版本之前共有4个IO Thread,分别是write、read、insert buffer和log IO thread。在Linux平台下,IO Thread的数量不能进行调整,但是在Windows平台下,可以通过参数innodb_file_io_threads来增大IO Thread。从InnoDB1.0.x版本开始,read Thread和write Thread分别增大到了4个,并且不再使用innodb_file_io_threads参数,而是分别使用innodb_read_io_threads和innodb_write_io_threads参数进行设置,如:
在这里插入图片描述
在这里插入图片描述

Purge Thread(清理线程,清理已经使用的undo页)

purge 英 [pɜːdʒ] 清除,清洗

事务被提交之后,其所使用的undolog可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页。
在InnoDB1.1版本之前,purge操作仅在InnoDB存储引擎的Master Thread中完成,而从InnoDB1.1版本开始,purge操作可以独立到单独的线程中执行,从而减轻Master Thread的工作,提高CPU的使用率以及提升存储引擎的性能。
用户可以在MySQL数据库的配置文件中添加如下命令来启动独立的Purge Thread:
在这里插入图片描述
在InnoDB版本中,即使将innodb_purge_threads设为大于1,InnoDB存储引擎启动的时候也会将其设置为1,并在错误文件中出现如下类似的提示:

在这里插入图片描述
从InnoDB1.2版本开始,InnoDB支持多个Purge Thread,这样做的目的是为了进一步加快undo页的回收,同时由于Purge Thread需要离散的读取undo页,这样也能进一步利用磁盘的随机读取性能,如用户可以设置4个Purge Thread:
在这里插入图片描述

Page Cleaner Thread(刷新脏页)

Page Cleaner Thread是InnoDB1.2.x版本引入的,作用是将之前版本中的脏页刷新操作都放入到单独的线程中来完成,其目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

2、内存

缓存池

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

缓存池简单老说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读取到的页放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页的时候,首先判断页是否在缓存池中,如果在缓冲池中,称该页在缓冲池中被命中,直接读取该页,否则,读取磁盘上的页

对于数据库中页的修改操作,首先修改在缓存池中的页,然后再以一定的频率刷新到磁盘上,这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次页发生修改的时候触发的,而是通过一种称为Checkpoint的机制刷新到磁盘。同样,这也是为了提高数据库的整体性能。

从上面可以看出,缓存池的大小直接影响数据库系统的性能,由于32位操作系统的限制,在该系统下最多将其值设置为3G,此外用户可以打开操作系统的PAE选项来获得32位操作系统下最大64G内存的支持。随着内存技术的成熟,成本也在不断下降,8G的内存变得很普遍,而在PC服务器上已经支持512G的内存,因此为了让数据库使用更多的内存,强烈建议数据库服务器都采用64位的操作系统。

对于InnoDB存储引擎而言,其缓存池的配置通过参数innodb_buffer_pool_size来设置,如下面将一个MySQL服务器的缓存池设置为15GB。

在这里插入图片描述

具体来看,缓存池中缓存的数据页类型有:

  • 索引页
  • undo页
  • 插入缓存
  • 自适应哈希索引
  • InnoDB存储的锁信息
  • 数据字典信息等。

不能简单的认为,缓存池只是缓存索引页和数据页,它们只是占缓存池很大的一部分而已。

在这里插入图片描述

从InnoDB1.0.x版本开始,允许有多个缓冲池实例,每个页根据哈希值评价分配到不同缓冲池实例中,这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力。可以通过innodb_buffer_pool_instances来进行设置,该值默认为1。
在这里插入图片描述

在配置文件中将innodb_buffer_pool_instances设置为大于1的值可以得到多个缓存池实例,通过命令show engine innodb status可以观察到下面的内容。
在这里插入图片描述
在这里插入图片描述

LRU List(存放从磁盘读取的数据)、Free List和Flush List (存脏页列表)

在前面一小节中我们知道了缓存池是一个很大的内存区域,其中存放各种类型的页,那么InnoDB的存储引擎怎么对这么大的内存区域进行管理的呢?

通常来说,数据库中的缓存池是通过LRU(最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓存池不能存放新读取到的页的时候,将首先释放LRU列表中尾端的页。(InnoDB进行了改进)

在InnoDB存储引擎中,缓存池中页的大小默认为16kb。同样使用LRU算法对缓存池进行管理,稍有不同的是InnoDB存储引擎对传统的LRU算法做了一些优化。
在InnoDB的存储引擎中,LRU列表中还加入了midpoint位置,新读取到的页,虽然是最新访问的页,但是并不是直接放入到LRU列表的首部,而是放入到LRU列表的midpoint位置,这个算法在InnoDB存储引擎下称为midpoint insertion strategy。
在默认配置下,该位置在LRU列表长度的5/8处,midpoint位置可由参数innodb_old_blocks_pct控制,如:

在这里插入图片描述
从上面的图片可以看出,参数innodb_old_blocks_pct默认值为37,表示新读取的页插入到LRU列表尾端的37%位置(大约3/8位置)。在InnoDB存储引擎中,把midpoint之后的列表称为old列表,之前的列表称为new列表,可以简单理解为new列表中的页都是最为活跃的热点数据。

为什么不放在首部?
因为这样如果select *一个表,可能会把以前缓存池中的数据全部覆盖掉即把所有热点数据移除,但是可能这些数据是很少使用的,并不是热点数据,那么下次要读原本缓存池中的数据就要重新从硬盘中拿,影响效率。

为了解决这个问题,InnoDB存储引擎引入了另外一个参数来进一步管理LRU列表,这个参数是innodb_old_blocks_time,用于表示页读取mid位置后需要多久等待才会被加入到LRU列表的热点部分,因此当执行上述所说的SQL操作的时候,可以通过下面的方法尽可能使LRU中热点数据不被刷出:
在这里插入图片描述
在这里插入图片描述

如果用户预估自己活跃的热点数据不止63%,那么在执行SQL语句前,还可以通过下面的语句来减少热点页被刷出的概率。
在这里插入图片描述
LRU列表用来管理已经读取的页,但是当数据刚启动的时候,LRU列表是空的,即没有任何的页,这时页都放在Free列表中。当需要从缓冲池中分页(因为缓冲池原本不是分好的,即分为16kb作为一页),首先从Free列表中查找是否有可用的空闲页,如果有,则将该页从Free列表中删除,放入LRU列表中。否则,根据LRU算法,淘汰LRU末尾的页。
当页从LRU列表的old部分加入到new部分的时候,称此时发生的操作为page made young,可以通过命令show engine innodb status来观察LRU列表即Free列表的使用情况和运行状态。
在这里插入图片描述
通过show engine innodb status可以看到:当前Buffer pool size共有327679个页,即327679*16kb,总共5GB的缓冲池,Free buffers表示当前Free列表中的页的数量,Database pages表示LRU列表中的页的数量。可能的出现的情况是Free buffers和Databases pages的数量的和不等于Buffer pool size。这是因为缓存池中的页还可能分给自适应哈希索引、Lock信息、Insert Buffer等页,而这部分页不需要LRU算法进行维护,因此不存在于LRU列表中。

page made young显示了LRU列表中页移动到前端的次数,因为该服务器在运行阶段没有改变innodb_old_blocks_time的值,因此not young为0。youngs/s、non-youngs/s表示每秒这两类操作的次数,这里还有一个重要的观察变量-Buffer pool hit rate,表示缓存池的命中率,这个例子中为100%。说明缓冲池运行状态非常良好,通常该值不应该小于95%,如果小于95%,用户需要观察是否因为全表扫描引起的LRU列表被污染的问题。

在这里插入图片描述
从InnoDB1.2版本开始,还可以通过表INNODB_BUFFER_POOL_STATS来观察缓冲池的状态,如:
在这里插入图片描述

此外还可以通过表INNODB_BUFFER_PAGE_LRU来观察每个LRU列表中每个页的具体信息,例如下面的语句可以看到缓冲池LRU列表中SPACE为1的表的页类型:
在这里插入图片描述

在这里插入图片描述
InnoDB存储引擎从1.0.x版本开始支持压缩页的功能,即将原本的页压缩为1kb、2kb、4kb、8kb,由于页的大小发生了变化,LRU列表也有些许的改变,对于非16kb的页,是通过unzip_LRU列表进行管理的。通过show engine innodb status可以观察到如下内容:
在这里插入图片描述
可以看到LRU列表中一共有1539个页,而unzip_LRU列表中有156个页,这里需要注意的是LRU页中的页包含了unzip_LRU列表中的页。
对于压缩页的表,每个表的压缩比例可能各不相同,可能存在有的表页大小为8kb,有的是2kb。

unzip_LRU是怎么样从缓存池中分配内存的呢?
首先,在unzip_LRU列表中对不同压缩页大小的页进行分别管理,其次,通过伙伴算法进行内存的分配,例如对需要从缓存池中申请页为4kb的大小,其过程如下:

  • 检查4kb的unzip_LRU列表,检查是否有可用的空闲页。
  • 若有,则直接使用
  • 否则,检查8kb的unzip_LRU列表
  • 若能够得到空闲页,将页分成2个4kb的页,存到4kb的unzip_LRU列表中。
  • 若不能得到空闲页,从LRU列表中申请16kb的页,将页分为1个8kb的页和两个4kb的页,分别存放在对于的unzip_LRU列表中。

同样可以通过information_shema架构下的表INNODB_BUFFER_PAGE_LRU来观察unzip_LRU列表中的页。如:

/在这里插入图片描述
脏页列表中的页是脏页
在LRU列表中的页被修改之后,称该页为脏页。即缓冲池中的页和磁盘上的页的数据产生不一致。这个时候数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而FLUSH列表中的页即为脏页列表,需要注意的是,脏页既在LRU列表中,也在FLUSH列表中。LRU列表用来管理缓冲池只能过的页的可用性,FLUSH列表用来管理将页刷新到磁盘。两者互不影响

和LRU列表一样,FLUSH列表也可以通过命令show engine innodb status来查看,前面例子中Modified db pages 24673就显示了脏页的数量,information_schema架构下并没有类似INNODB_BUFFER_PAGE_LRU的表来显示脏页的数量即脏页的类型,但是正如前面所述的那样,脏页同样存在于LRU列表中,故用户可以通过元数据表INNODB_BUFFER_PAGE_LRU来查看,唯一不同的是需要加入OLDEST_MODIFICATION大于0的SQL查询条件,如:在这里插入图片描述在这里插入图片描述可以看到当前共有5个脏页即他们对应的表和页的类型,TABLE_NAME为NULL表示该页属于系统表空间。

3、重做日志缓冲(在内存中,但是不在上面讲的缓冲池中,记录的是数据页的物理修改)

redo log是重做日志,提供前滚操作,undo log是回滚日志,提供回滚操作。redo通常是物理日志,记录的是数据页的物理修改,而不是某一行或者某几行修改成怎么样,用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后以此提交的位置。)
undo用来回滚行记录到某个版本。undo log一般是逻辑日志,根据每行记录进行记录。

InnoDB的存储引擎的内存区域除了缓冲池之外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区(不是缓存池),然后按一定频率将其刷新到重做日志文件,重做日志缓冲一般都不会设置很大,因为一般情况下,每秒钟都会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可,该值可以由配置参数innodb_log_buffer_size控制,默认为8MB
在这里插入图片描述
在通常情况下,8MB的重做日志足以,满足绝大部分的应用,因为重做日志在下列三种情况下会讲重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中。

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

4、额外的内存池(分配给一些数据结构内存)

额外的内存值很重要,在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆的方式进行的,在对一些数据结构本身的内存进行分配的时候,需要从额外的内存池中进行申请。当该区域的内存不够的时候,会从缓存池中进行申请。
比如。分配了缓冲池,但是每个缓冲池中的帧缓冲还有对应的缓冲控制对象,这些对象记录了一些诸如LRU、锁、等待等信息,而这个对象的内存需要从额外的内存池中申请,因此,在申请了很大的InnoDB缓冲池的时候,也应该考虑相应增加这个值。

四、Checkpoint技术 (将脏页刷回磁盘,为了避免宕机的时候数据丢失,使用Write Ahead log策略,先写日志,再修改页,保证ACID中的D)

前面已经讲到了,缓冲池的目的是为了协调CPU和磁盘速度的鸿沟,因此页的操作首先都是在缓存池中完成的,如果DML语句,如update或者delete改变了页中的记录,那么此时页是脏的,即缓冲池中的页的版本要比磁盘的新,数据库需要将新版本的页从缓冲池刷新到磁盘。

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

完全通过重做日志来恢复整个数据库系统中的数据到宕机发生的时刻有两个前提:

  • 缓存池可以缓存数据库中所有的数据(因为数据不存在硬盘,那就要存在缓存池)
  • 重做日志可以无限增大

即使满足上述两个条件,那么还有一个情况需要考虑,就是宕机后数据库的恢复时间,当数据库运行了几个月甚至几年的时候,发生了宕机,重新应用重做日志的时间回非常久,此时恢复的代价也会很大。
以此Checkpoint(检查点)技术的目的是解决以下几个问题:

  • 缩短数据库恢复时间
  • 缓冲池不够用的时候,将脏页刷新到磁盘
  • 重做日志不可用的时候,刷新脏页

当数据库发生宕机的时候,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经被刷新回了磁盘,故数据库只需要对Checkpoint之后的重做日志进行恢复,这样就大大缩短了恢复的时间
此外,当缓冲池不够用的时候,根据LRU算法会溢出最近最少使用的页,如果这个页是脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。

对于InnoDB存储引擎而言,其是通过LSN(Log Sequence Number)来标记版本的,而LSN是8字节的数字,其单位是字节,每个页有LSN。重做日志有LSN,Checkpoint有LSN,可以通过命令SHOW ENGINE INNODB STATUS来观察:
在这里插入图片描述

InnoDB内部的两种Checkpoint,分别为:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint发生在数据库关闭的时候将所有的脏页刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown = 1.
但是如果在运行的时候也使用Sharp Checkpoint,那么数据库的可用性回受到很大的影响,故在InnoDB存储引擎内部使用Fuzzy Checkpoint进行页的刷新,即只刷新一部分脏页么不是刷新所有的脏页回磁盘。

五、Master Thread 工作方式

InnoDB的主要工作都是在一个单独的后台线程Master Thread中完成的。

1、InnoDB1.0.x版本之前的Master Thread

Master Thread内部组成:

  • 主循环(loop)
  • 后台循环(background loop)
  • 刷新循环(flush loop)
  • 暂停循环(suspend loop)

Master Thread会在这个这四个状态中进行切换。

主循环(大多数操作在这个循环中)

包含两大部分的操作–每秒钟的操作和10秒钟的操作(通过Thread sleep来实现)。
在这里插入图片描述
每秒钟的操作:

  • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是,这是为什么再大的事务提交时间也是很端的原因)
  • 合并插入缓冲(可能,如果前一秒IO次数小于5,说明压力小,可以执行插入缓冲)
  • 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能,判断缓冲池中脏页的比例是否超过了配置文件中buf_get_modified_ratio_pct的设置)
  • 如果当前没有用户获得,则切换到background loop(可能)

在这里插入图片描述

每10秒钟的操作:

  • 刷新100个脏页到磁盘(可能,如果前面10秒IO次数小于200)
  • 合并至多5个插入缓冲(总是)
  • 将日志缓冲刷新到磁盘(总是)
  • 删除无用的Undo页(总是,full purge操作)
    对表进行update和delete操作的时候,原先的行被标记为删除,但是因为一致性读的关系,需要保留这些行版本的信息。比如有时还有查询操作需要读取事务系统中已经被删除的行是否已经删除。
  • 刷新100个或者10个脏页到磁盘(总是,脏页比例超过70%,刷新100脏页到磁盘,否则刷新10)
  • 刷新100个或者10个脏页到磁盘(总是)

在这里插入图片描述

六、InnoDB关键特性(三大特性:Insert Buffer、两次写、自适应哈希索引)

  • 插入缓冲
  • 两次写
  • 自适应哈希索引
  • 异步IO
  • 刷新邻接页

1、插入缓冲

插入缓冲原理

只看下面这段:
对于为非唯一索引,辅助索引的修改操作并非实时更新索引的叶子页,而是把若干对同一页面的更新缓存起来做,合并为一次性更新操 作,减少IO,转随机IO为顺序IO,这样可以避免随机IO带来性能损耗,提高数据库的写性能

Insert Buffer(为辅助索引(且不是唯一?)的插入服务的,将多个插入合并到一个操作,因为非聚集索引叶子节点是无序的)

InnoDB缓冲池中有Insert Buffer固然不错,但是insert Buffer和数据页一样,也是物理页(实实在在的内存)的一个组成部分。

主键是行唯一的标识符,通常应用程序中记录的顺序是按照主键递增的顺序进行插入的。因此插入聚集索引一般是顺序的,不需要磁盘的随机读取其他页中的记录,对于这样的插入,速度非常快。

如果使用非聚集索引查找,数据还是按照主键进行顺序存放的,但是非聚集索引叶子节点的插入不是顺序的,这就需要离散的访问非聚集索引页。

聚集索引叶子节点是有序的,非聚集索引叶子节点是无序的

Innodb插入设计Insert buffer,对于非聚集索引的插入或者更新,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,如果在,则直接插入,如果不在,先放到Insert Buffer对象中,好似欺骗。然后再以一定的频率和情况进行Insert Buffer和辅助叶子节点的merge操作。这时通过能将多个插入合并到一个操作(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能,减少IO次数
在这里插入图片描述

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

  • 索引是辅助索引
  • 索引不是唯一的
    通过show engine innodb status来查看插入缓冲的信息

在这里插入图片描述
在这里插入图片描述
seg size显示当前Insert Buffer的大小11336*16kb,大约为177MB,free list len表示空闲列表的长度,size表示已经合并并记录页的数量,merges代表合并的次数,也就是实际读取页的次数,merges:merged recs大约为1:3,表示插入缓冲对于非聚集索引页的离散IO请求大约降低了2/3。

例子

例如name字段的插入顺序为:

(‘Maria’,10), (‘David’,7), (‘Tim’, 11), (‘Jim’, 7), (‘Monty’, 10), (‘Herry’, 7), (‘Heikki’, 7)

后面的数字表示原先插入的辅助索引的page_no,可以看到页的访问是完全无序的,然而当插入到insert buffer中时,上述记录可能在一个页中,因此减少了离散读取。在insert buffer中,记录根据应插入辅助索引的叶子节点page_no进行排序,故上述记录在insert buffer中的状态应为:

(‘David’,7), (‘Jim’, 7), (‘Herry’, 7), (‘Heikki’, 7) , (‘Maria’,10), (‘Monty’, 10), (‘Tim’, 11)

当要进行合并时,页page_no为7的记录有4条,可以一次性将这4条记录插入到辅助索引中,从而提高数据库的整体性能。

Change Buffer(对DML操作都进行缓冲,不止是Insert操作)

Innodb从1.0.x版本开始引入Change Buffer,可以将其视为Insert Buffer1的升级,从这个版本开始,InnoDB存储引擎可以对DML操作-------Insert、Delete、Update都进行缓冲,它们分别是Insert Buffer、Delete Buffer、Purge buffer。

Insert Buffer的内部实现(4.1开始全局只有一棵B+树,存放在共享表空间,ibdata1)

Insert Buffer的使用场景即非唯一辅助索引的插入操作。
在InnerDB4.1之前的版本中,每张表有一棵Insert Buffer B+树。而在现在的版本中,全局只有一棵Insert Buffer B+树,辅助对所有的辅助索引进行Insert Buffer,而这棵树存放在共享表空间中,默认为ibdata1,因为Insert Buffer非叶子节点的Search key中存了属于哪个表,在哪个页

存在的问题:
因此,试图通过独立表空间ibd文件恢复表中数据时,往往会导致check table 失败。这是因为表的辅助索引中的数据可能还在insert buffer中(没刷回硬盘?)。所以通过idb文件进行恢复后,还需要进行repair table 操作来重建表上所有的辅助索引。

Insert Buffer B+树的非叶子节点存放的是查询的search key(键值)
下图是Insert Buffer非叶子节点中的search key。

space(4字节,存放表空间id,每个表有唯一的表空间id,用来知道是哪张表)marker(1字节,兼容老版本)offset(4字节,表示页所在的偏移量)

search占9个字节。其中

  • space表示待插入记录所在的表的表空间id,每个表有唯一的space id,可以通过space id得知是哪张表。space占用四字节
  • marker占用一个字节,用来兼容老板本的Insert Buffer
  • offset表示页所在的偏移量,占用四字节。

当一个辅助索引要插入到页的时候,如果这个页不在缓冲池中,那么InnoDB存储引擎首先根据上述规则构造一个search key,接下来查询Insert Buffer b+树,然后再将这条记录插入到Insert Buffer B+树的叶子节点中。

对于插入到Insert Buffer B+树的叶子节点的记录。并不是直接将待插入的记录插入,而是根据如下规则进行构造。

在这里插入图片描述

在这里插入图片描述

  • IBUF_REC_OFFSET_COUNT保存两个字节的整数,用来排序每个记录进入Insert Buffer的顺序。

从第五列开始就是实际插入记录的各个字段,因为需要与原来的插入记录进行比较,Insert Buffer B+树的叶子节点需要额外消耗13字节的开销。

启用insert buffer索引后,辅助索引页(space、page_no)中的记录可能被插入到insert buffer B+树中,所以为了保证每次merge insert buffer页必须成功,还需要有一个特殊的页来标记每个辅助索引页(space、page_no)的可用空间。这个页的类型为insert buffer bitmap

在这里插入图片描述

Merge Insert Buffer(Insert Buffer中的记录合并到真正的辅助索引)

Insert Buffer中的记录什么时候合并到真正的辅助索引中呢?

  • 辅助索引页被读取到缓冲池时,将Insert Buffer B+树中该页的记录插入到该辅助索引页中去。
  • Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间的时候
  • Master Thread

2、两次写(提高脏页刷新到磁盘的可靠性,保存页的副本,写入失败,通过副本页还原)

数据库发生宕机的时候,可能InnoDB存储引擎正在写入到某个页到表中,而这个页只写了一部分,比如16kb的页,只写了前4kb,之后就发生宕机,这时候虽然有redo log,但是redo log只是记录数据页的修改,比如偏移量800,修改称‘aaa’记录,如果这个也本身发生了损坏,在对其重做是没意义的。

这就是说,在应用apply重做日志之前,需要一个页的副本,当写入失效发生的时候,下面通过页的副本来还原该页,再进行重做,这就是doublewrite。

double write的组成

  • 内存中的doublewrite buffer,大小为2M。
  • 另一个是物理磁盘共享表空间中连续的128页,即两个区,同样是2M

在对缓冲区的脏页进行刷新的时候,并不直接写磁盘,而是通过memcpy函数将脏页复制到doublewrite buffer。之后通过double write buffer在分两次,每次1MB顺序写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。
在这里插入图片描述

两次写原理

两次写讲解博客
其原理是这样的:
1)当刷新缓冲池脏页时,并不直接写到数据文件中,而是先拷贝至内存中的两次写缓冲区
2)接着从两次写缓冲区分两次写入磁盘共享表空间中,每次写入1MB
3)待第2步完成后,再将两次写缓冲区写入数据文件

这样就可以解决上文提到的部分写失效的问题,因为在磁盘共享表空间中已有数据页副本拷贝,如果数据库在页写入数据文件的过程中宕机,在实例恢复时,可以从共享表空间中找到该页副本,将其拷贝覆盖原有的数据页,再应用重做日志即可

3、自适应哈希索引(根据缓存池中的B+树实现)

B+树的查找次数取决于B+树的高度,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引,称之为自适应哈希 (Adaptive Hash Index)

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

AHI有一个要求,对这个页的连续访问模式必须是一样的,例如对于(a,b)这样的联合索引页,其访问模式可以是以下情况

  • where a=xxx
  • where a=xxx and b=xxx

访问模式一样指的是查询条件一样,其交替进行上述两种查询,那么InnoDB引擎不会对该页构造AHI。此外AHI还有如下的要求:

  • 以该模式访问了100次
  • 页通过该模式访问了N次,其中N=页中记录/16

启用AHI之后,读取和写入速递可以提升2倍,辅助索引的链接操作性能可以提高5倍。

4、异步IO(一个IO请求之后立即发出另一个IO请求,当所有IO请求发送完毕,等待所有IO操作完成)

当前的数据库系统都采用异步IO(AIO)的方式来处理磁盘操作。
与AIO对应的是Sync IO,即每进行以此IO操作,需要等待此次操作结束才能继续接下来的操作

用户可以在发出一个IO请求之后立即再发出另一个IO请求,当全部IO请求发生完毕之后,等待所有IO操作的完成,这就是AIO。

AIO的另外一个优势是进行IO Merge操作,也就是将多个IO合并为1个IO。这样就可以提高IOPS的性能,例如用户需要访问页的(space,page_no)为:
(8,6)、(8,7)、(8,8)
每个页大小为16kb,那么同步IO需要进行三次IO操作,而AIO底层会发送一个IO请求,从(8,6)开始,读取48kb的页。

5、刷新邻接页(邻接的脏页一起刷新)

InnoDB存储引擎提供了Flush Neighbor Page(刷新邻接页)的特性。
工作原理:

  • 当刷新一个脏页的时候,InnoDB存储引擎会检测该页所在区的所有页,如果是脏页,那么一起执行刷新。好处就是将多个IO写入才做合并为一个IO操作。
    存在的两个问题:
  • 是不是可能将不怎么脏的页进行写入,而该页写入之后很快又变成脏页?
  • 固态硬盘有着较高的IOPS,是否需要这个特性

innodb_flush_neighbors用于控制是否开启这个特性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值