MySQL的InnoDB存储引擎

MySQL组成部分

  • 连接池组件
  • 管理服务和工具组件
  • SQL接口组件
  • 查询分析器组件
  • 优化器组件
  • 缓冲组件
  • 插件式存储引擎
  • 物理文件

其中存储引擎是基于表而不是数据库

MySQL存储引擎概述

插件式存储引擎是MySQL数据库的特性之一,每个存储引擎都有各自的特点,用户可以根据应用的需要选择不同的存储引擎,由于MySQL数据库的开源,用户可以根据预定义的存储引擎接口编写自己的存储引擎,MySQ5.0支持的存储引擎包括:

MyISAM、InnoDB、BDB(停止开发)、Memory、Merge、Example、NDB Cluster、Archive、CSV等

其中InnoDB和BDB提供事务安全表,而其他存储引擎都是非事务安全表。注意如果在创建表时未指定存储引擎,系统将使用默认的存储引擎,在MySQL之前默认的存储引擎是MyISAM,而MySQL5.5之后默认的存储引擎是InnoDB

查看存储引擎

使用show engines可以查看数据库之前哪些存储引擎和默认使用的存储引擎,同时可以看出哪些存储引擎支持事务,XA分布式事务。

MySQL的InnoDB存储引擎_第1张图片

不同存储引擎的特性

常用存储引擎的对比如下图

MySQL的InnoDB存储引擎_第2张图片

MyISAM存储引擎

MyISAM不支持事务和表锁设计、不支持外键、支持全文索引,主要面向OLAP(On-Line Analytical Processing,在线分析处理)数据库应用,优势就是访问速度快,对于不要求事务的需求可以使用该引擎创建表,如数据仓库报表查询等。MyISAM是MySQL5.5.8之前的版本默认的存储引擎,之后改成了InnoDB存储引擎

MyISAM存储引擎的缓冲池只缓存了索引文件,而没有缓冲数据文件,数据文件缓存交由操作系统本身完成, 这和其他使用LRU算法缓存数据的大部分数数据库不同,在MySQL5.1.23之前缓存索引的缓冲区最大只能设置4GB,在之后的版本64位操作系统最大支持大于4GB

每个MyISAM存储引擎的在磁盘上存储为三个文件:

  • .frm文件(表结构的数据文件)
  • .MYD文件(用来存储数据文件)
  • .MYI(用来存储索引文件)

数据文件和索引文件可以存放到不同的目录(在创建表的时候通过DATA DIRECTORY和INDEX DIRECTORY语句指定绝对路径),平均分布IO,提高速度。

MyISAM的表支持三种不同的存储格式

  • 静态(固定长度)表
  • 动态表
  • 压缩表

其中静态表是默认的存储格式,静态表中的字段都是固长字段,优点存储速度高,容易缓存,故障后容易恢复,缺点是占用空间通常比动态表,静态表的数据在存储时会按照列的长度进行补足空格,访问的时候会被去掉,所以使用静态表在存储尾部带有空格的内容的时候会被自动去掉,需要注意。

动态表即是不是固定长度,按照实际长度存储,这样的优点就是节省空间,但是频繁的更新和删除记录会产生碎片。需要定期使用OPTIMIZE TABLE或者myisamchk-r命令改善性能。

压缩表由myisampack工具创建,占据非常小的磁盘空间,每个记录都被单独压缩过,只有非常小的访问开支

从MySQL5.0之前,MyISAM默认支持的表的大小为4GB, 从MySQL5.0之后的版本,MyISAM默认支持的表的大小为256TB,满足一般应用需求

InnoDB存储引擎

InnoDB支持事务和行锁设计、支持外键、支持全文索引(InnoDB1.2之后版本),主要面向OLTP(On-Line Analytical Processing,在线事务处理)数据库应用,支持类似Oracle的非锁定读,即默认读取操作不会产生锁。

从MySQL5.5.8版本开始,InnoDB存储引擎就是默认的存储引擎, InnoDB存储引擎将数据放在一个默认的表空间中,这个表空间像黑盒一样由InnoDB存储引擎管理

InnoDB通过使用多版本并发控制MVCC来获的高并发性,并且实现SQL标准的四种隔离级别,默认是REPEATABLE, 同时使用next-key locking的策略来避免幻读

除此之外,InnoDB存储引擎还提供了以下四种高性能和高可用的功能

  • 插入缓冲(insert buffer)
  • 二次写(double write)
  • 自适应哈希索引(adaptive hash index)
  • 预读(read ahead)

对于表中数据的存储,InnoDB存储引擎采用聚集clustered的方式,因此每个表的数据都是按照主键的顺序进行存放,如果没有显示的指定主键,InnoDB默认会为每一行生产一个6字节的ROWID并以此作为主键;每个InnoDB存储引擎的在磁盘上存储为两个文件:

  • .frm文件(表结构的数据文件)
  • .ibd文件(用来存储数据和索引文件)

对于InnoDB表,自动增长列必须是索引。如果是组合索引,也必须是组合索引的第一列,但是对于MyISAM 表,自动增长列可以是组合索引的其他列,这样插入记录后,自动增长列是按照组合索引的前面几列进行排序后递增的。

Memory存储引擎

Memory存储引擎是将表中的数据存放在内存中,如果数据库重启或者发生崩溃,表中数据将丢失,非常适合存储临时数据以及数据仓库的维度表(之前称之为HEAP存储引擎)

Memory默认使用哈希索引,而不是B+树索引,只支持表锁,并发性能差,不支持Blob和Text列类型,存储变长字段vachar会按照定长char方式进行,浪费内存

使用Memory存储引擎来存放查询中间结果集,如果结果集大于Memory存储引擎的容量设置或者中间结果集中含有TEXT和BOLB类型的字段 则MySQL数据库会把其转换中MyISAM存储引擎并存放到磁盘中,由于MyISAM不缓存数据文件,可能会导致性能损失

每个Memory存储引擎的在磁盘上只有一个文件:

  • .frm文件(表结构的数据文件)

在启动MySQL服务时使用--init-file选项,把INSERT INTO ... SELECT 或者LOAD DATA INFILE 这样的语句放入这个文件,就可以在服务启动时候从数据源中装载表,当然不需要这个临时表可以使用DELETE FROM 或者TRUNCATE TABLE或者DROP TABLE清空表。

每个Memory表可以放置数据量的大小收到max_heap_table_size系统变量约束,默认初始值是16MB。可以在创建Memory表时通过MAX_ROWS字句指定表的最大行数

Merge存储引擎

Merge存储引擎:是一组MyISAM存储引擎的表的组合,这些MyISAM表结构必须完全相同,Merge表本身并没有数据,对Merge表的查询、更新、删除操作实际上是针对内部的MyISAM表进行的。

对Merge表插入操作是通过INSERT_METHOD子句定义插入的表,有三个不同的值,使用FIRST和LAST值使得插入操作被相应地作用到第一个或者最后一个表上,不定义字句或者定义为NO表示不能对这个Merge表插入。

对Merge表进行Drop操作,删除的只是Merge表的定义,对内部的表没有任何影响。

每个Merge存储引擎的表在磁盘上有两个文件:

  • .frm文件(表结构的数据文件)
  • .MRG文件(包含组合表的信息,包括Merge表由哪些表组成,插入新数据时的依据,可以通过修改MRG文件来修改MRG表,修改完后要刷新FLUSH_TABLES)

其他存储引擎

  • NDB存储引擎:是一个集群存储引擎,数据全部放在内存中,从MySQL5.1版本开始可以将非索引数据放到磁盘上,因此主键查找速度非常快 通过添加NDB存储节点可以线性提高数据库性能
  • Archive存储引擎:只支持INSERT和SELECT操作,从MySQL5.1版本开始支持索引,使用zlib算法将数据行(row)进行压缩后存储,压缩比达1:10 ,适合存储归档数据,如日志信息,Archive使用行锁实现高并发的插入操作,但是又不是事务安全,目标就是提供高速的插入和压缩功能
  • Maria存储引擎:MyISAM的后续版本,特点是支持缓存数据和索引文件,应用了行锁,提供了MVCC的功能,支持事务和非事务能更好的处理BLOB类型的性能

其他的CSV、Federated、Sphinx等等不在介绍

InnoDB存储引擎

InnoDB的版本

在MySQL5.1版本中,MySQL数据库允许存储引擎以动态形式加载,存储引擎的更新不受MySQL数据库版本的限制,所有在MySQL5.1版本中存在两个版本的InnoDB,一个是静态的InnoDB,另外一个是动态加载的InnoDB,也称之为InnoDB Plugin,也称之为InnoDB1.0.X版本,随后MySQL5.5版本中InnoDB升级为InnoDB1.1.X版本;MySQL5.6版本中InnoDB升级为InnoDB1.2.X版本,主要不同如下:

版本 功能
老版本 InnoDB 支持ACID、行锁设计、MVCC
InnoDB 1.0.x 继承了上述版本所有功能,增加了compress和dynamic页格式
InnoDB 1.1.x 继承了上述版本所有功能,增加了Linux AIO、多回滚段
InnoDB 1.2.x 继承了上述版本所有功能,增加了全文索引支持、在线索引添加

查看InnoDB版本:使用show variables like 'innodb_version'

InnoDB的架构

MySQL的InnoDB存储引擎_第3张图片

由上图可以看出InnoDB架构组成是:

  • InnoDB存储引擎内存池(包括:缓冲池、redo log缓冲、额外内存池)
  • 后台线程(Master Thread、IO Thread、purge Thread、page cleaner Thread)
  • 存储文件

内存池

由多个内存块组成的内存池,主要负责

  • 维护所有进程或线程需要访问多个内部数据结构
  • 缓存磁盘数据,同时缓存磁盘文件数据修改前的数据
  • redo log缓冲

后台线程

主要作用是负责刷新内存池中的数据,保证缓冲池的内存缓存是最近的数据,此外将修改的文件刷新到磁盘,同时保证数据库异常情况下InnoDB能够恢复到正常状态;InnoDB存储引擎是多线程的模型,后台有多个不同的后台线程,负责不同任务,主要包括:

  • Master Thread:将缓冲池中的数据异步刷新到磁盘保证一致性,其中包括了:脏页的刷新(后面交给page cleaner线程)、合并插入缓冲(INSERT BUFFER)、undo 页的回收
  • IO Thread:InnoDB大量使用AIO来处理写IO请求,提高数据库性能,主要包括如下4个IO Thread,分别是write、read、insert buffer 和log IO thread,从InnoDB 1.0.x版本开始,read thread和 write thread分别增大到了4个,使用innodb_read_io_threads 和innodb_write io_threads参数进行设置,通过使用show engine innodb status 来查看IO Thread,如下图:

MySQL的InnoDB存储引擎_第4张图片

  • Purge Thread:事务提交后通过Purge Thread来回收undo log,从InnoDB 1.1.x版本开始,purge操作可以独立到单独的线程中进行,减轻Master Thread的工作(之前purge操作在Master Thread中完成),如下在MySQL的配置文件中添加如下命令来启用独立的Purge Thread

[mysqld]
innodb_purge_threads=4 #在InnoDB 1.2版本开始,InnoDB支持多个Purge Thread,加快undo log的回收,

通过如下命令查看Purge Thread线程:show variables like 'innodb_purge_threads'

  • Page Cleaner Thread:在InnoDB 1.2.x版本中引入的。作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。减轻Master Thread的工作

Master Thread

Master Thread是最高优先级别的线程,主要负责脏页的刷新(后面交给page cleaner线程)、合并插入缓冲(INSERT BUFFER)、undo 页的回收,内部主循环每秒一次或每10秒执行一次的操作

每秒一次的操作包括:

  • redo log缓冲中的内容刷新到磁盘的日志文件,即使这个事务还没有提交(总是);
  • 合并插入缓冲(可能)
    • innoDB存储引擎会判断当前一秒内发生的IO次数是否小于5次,如果小于5次,InnoDB认为当前的IO压力很小,可以执行合并插入缓冲的操作
  • 如果当前没有用户活动,切换到backgroup look(可能)
  • 刷新脏页(可能)(innoDB1.2.x 交由单独的page cleaner线程完成)

每十秒一次的操作包括:

  • 合并至多5个插入缓冲
    • InnoDB存储引擎会先判断过去10秒之内磁盘的IO操作是否小于200次,如果是,InnoB存储引擎认为当前有足够的磁盘IO操作能力,因此将100个脏页刷新到磁盘。接着,InnoDB存储引擎会合并插入缓冲。不同于每秒一次操作时可能发生的合并插入缓冲操作,这次的合并插入缓冲操作总会在这个阶段进行。之后InnoDB 存储引擎会再进行一次将日志缓冲刷新到磁盘的操作。这和每秒一次时发生的操
      作是一样的。
  • 将redo log日志缓冲刷新到磁盘
  • 删除无用的undo log
    • InnoDB存储引擎会进行一步执行full purge操作,即删除无用的undo页。对表进行update、delete这类操作时,原先的行被标记为删除,但是因为一致性读(consistent read)的关系,需要保留这些行版本的信息。但是在full purge过程中,InnoDB存储引擎会判断当前事务系统中已被删除的行是否可以删除,比如有时候可能还有查询操作需要读取之前版本的undo信息,如果可以删除,InnoDB会立即将其删除,从源代码可发现,InnODB仔储引擎在执行 full purge操作时,每次最多尝试回收20个undo页。
  • 刷新100个或者10个脏页到磁盘(innoDB1.2.x 交由单独的page cleaner线程完成)

刷新脏页操作

innoDB1.2.x 后刷新脏页操作不再有Master Thread线程完成,而是交由单独page cleaner线程完成,主要有如下2个参数

参数innodb_io_capacity:从inInnoDB1.0.x版本开始提供用来表示磁盘IO的吞吐量,默认值为200.对于刷新到磁盘页的数量,会按照innodb_io_capacity的百分比来进行控制。规则如下:

  • 在合并插入缓冲时,合并插入缓冲的数量为innodb_io_capacity值的5%;
  • 在从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_capacity.

参数innodb_max_dirty_pages_pc:最大脏页数,默认是75,inInnoDB判断当前缓冲池中脏页的比例(buf_get_modified_ratio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数,如果超过了这个阈值,InnoDB存储引擎认为需要做磁盘同步的操作,刷新脏页

参数innodb_adaptive_flushing:自适应地刷新,通过buf_flush_get_desired flush rate的函数来判断需要刷新脏页最合适的数量。此时当脏页的比例小于innodb_max_dirty_pages也会刷新一定脏页

回收undo页

参数innodb_purge_batch_size:之前每次进行full purge操作时,最多回收20undo页,该参数可以控制每次full purge回收的undo页的数量。该参数的默认值为20,并可以动态地对其进行修改

background loop

若当前没有用户活动(数据库空闲时)或者数据库关(shutdown),就会切换到这个循环。background loop会执行以下操作:

  • 删除无用的Undo页(总是);
  • 合并20个插入缓冲(总是);
  • 跳回到主循环(总是);
  • 不断刷新100个脏页直到符合条件(innoDB1.2.x 交由单独的page cleaner线程完成),跳转到flush loop(刷新脏页的循环)

InnoDB存储引擎中的内存

InnoDB存储引擎内存池由缓冲池、重做日志缓冲池(分别由配置文件中的参数innodb_buffer_pool_size和innodb_log_buffer_size大小决定)、额外的内存池组成,如下:

MySQL的InnoDB存储引擎_第5张图片

缓冲池

缓冲池作用

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。在数据库中进行读取数据页的操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在,直接读取该页。否则读取磁盘上的页。对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。页从缓冲池刷新回磁盘的操作并不是在每次页发生变更时触发,而是通过checkpoint机制刷新回磁盘,提高了数据库的性能。

缓冲池缓存页的类型

缓冲池中缓存的页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等。

设置缓冲池的大小

通过set innodb_buffer_pool_size =20000000设置缓冲池的大小,同样可以查看缓冲池大小:show variables like 'innodb_buffer_pool_size',

MySQL的InnoDB存储引擎_第6张图片

或者通过 :show engine innodb status查看InnoDB存储引擎相关信息,以下是缓冲池大小相关信息:

MySQL的InnoDB存储引擎_第7张图片

自InnoDB1.0.x版本之后,允许存在多个缓冲池实例,每个页根据hash值分配到不同的缓冲池,这样能够提升数据库并发性,通过命令查看缓冲池实例个数如下,默认是1个,通过参数innodb_buffer_pool_instances进行设置和查询。

MySQL的InnoDB存储引擎_第8张图片

或者通过innodb_buffer_pool_stats 表查看缓冲池实例相关信息,默认只有一个,POOL_ID为0,如下:

select POOL_ID, POOL_SIZE, FREE_BUFFERS, DATABASE_PAGES from information_schema.innodb_buffer_pool_stats

缓冲池的管理LRU列表

通常来说数据库使用LRU算法(Latest Recent Used最近最少使用)来管理缓冲池,即最多次使用的页在LRU列表前端,最少的在尾端,当缓冲池不能存放新读取的页时,释放LRU列表尾部的页

在InnoDB存储引擎中,缓冲池中页的默认大小是16kb,同样使用LRU算法管理。在InnoDB的LRU算法中,每次读取的页不是放在LRU列表的前端,而是放在在LRU列表中定义的一个midpoint的位置,默认该位置在LRU长度的5/8处,由参数innodb_old_blocks_pct控制,查看innodb_old_blocks_pct值如下,通过set global innodb_old_blocks_pct =20可以设置为20

MySQL的InnoDB存储引擎_第9张图片

可以看出该值为37,表示midpoint位于LRU列表尾端的37%,InnoDB把LUR列表midpoint之后的列表成为old列表,之前的成为new列表,new列表也是热点的数据。37%则表示前5/8热点数据,后3/8为非热点数据。

采用这种LRU算法是因为:如果直接将读取的数据页放在LRU列表首部,某些SQL(比如全表扫描SQL)产生的大量的页数据可能会将热点数据被刷出,下次读取因为没有缓存而需要再次读取磁盘。针对这个问题可以通过innodb_old_blocks_time这个参数进一步管理LRU列表,这个参数表示页读取midpoint位置后经过多久时间才会被加到LRU列表的热点端

set global innodb_old_blocks_time =2000

缓冲池的管理FREE列表

LRU列表用来管理已经读取的页,当数据库启动时,LUR列表是空的,此时页数据都存放到Free列表中,当需要从缓冲池中分页时,首先从Free列表中查找可用的空闲页,若有则将该页从Free列表中删除放入到LRU列表中,当页从LRU列表的old部分加入到new部分时,此操作称之为page made young,而因innodb_old_blocks_time的设置导致页没有从old部分移动到new部分的操作称为page not made young,如下图:其中Database pages表示LRU列表中页的数量

MySQL的InnoDB存储引擎_第10张图片

通过以下SQL查看缓冲池的运行状态,HIT_RATE表示缓冲区的命中率,一般情况下是大于95%。如果低于该值需要检查是否因为由于全表扫描导致的LRU列表被污染。

select POOL_ID, HIT_RATE,PAGES_MADE_YOUNG,PAGES_NOT_MADE_YOUNG from information_schema.innodb_buffer_pool_stats

通过以下SQL查看LRU列表中每个页的详细信息,如下:

select * from information_schema.INNODB_BUFFER_PAGE_LRU

InnoDB支持页压缩,将原本16KB的页压缩成1KB、2KB、4KB、8KB,对于小于16KB的页通过unzip_LRU列表进行管理。

缓冲池的管理FLUSH列表

LRU列表中的页被修改后,称该页为脏页,即缓冲池中的页和磁盘上的页的数据产生不一致,这时数据库会通过Checkpoint机制将脏页刷新回磁盘,而Flush列表中的页即是脏页列表,可以看出脏页即存在于LRU列表中,也存在于Flush列表中。

重做日志缓冲池

InnoDB存储引擎首先将redo log信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件,重做日志缓冲一般不需要设置很大,因为一般情况下每一秒就会将redo log缓冲刷新到日志文件,所以只要能保证一秒内产生的事务量能在缓冲保存即可,该值由innodb_log_buffer_size控制,默认是8MB。redo log缓冲会在以下三种情况刷新到redo log文件中

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

额外的内存池

在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆的方式进行,对一些数据结构本身的内存进行分配时需要从额外的内存池中进行申请,当该区域内存不够时才会从缓冲池申请

CheckPoint技术

前面提到对于数据库中页的修改操作,如一个DML语句的UPDATE或者DELETE操作改变了页的记录,这时页是脏的,即缓冲比磁盘的版本新,数据库需要将缓存池中的版本刷新到磁盘。

首先修改在缓冲池中的页,然后再以一定的频率通过checkpoint机制刷新回磁盘,

Write Ahead Log策略

为了避免从缓冲池刷新到磁盘时发生宕机,导致数据无法恢复,当前数据数据库系统普遍采用Write Ahead Log策略,即当事务提交时,先写redo log,再修改页。当发生宕机导致数据丢失,可以通过redo log来完成恢复,这也是事务ACID中的D(Durability持久性)的要求。

Checkpoint解决的问题:

  • 缩短数据库的回复时间(数据库宕机只需要对Checkpoint之后的redo log进行恢复,之前的已经刷回磁盘)
  • 缓冲池不够用时,将脏页刷新到磁盘(LRU算法会溢出最近最少使用的页,如果此页为脏页,则强制执行Checkpoint将其刷新回磁盘)
  • redo log不可用时,刷新脏页(redo log是循环利用,重新覆盖的日志如果还需要使用就需要强制执行Checkpoint将其刷新回磁盘)

Checkpoint分类

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint发生在数据库关闭时将所有的脏页都刷新回磁盘,是默认的工作方式,即参数innodb_fast_shutdown =1 

Fuzzy Checkpoint是在数据库运行中进行的页刷新,只刷新一部分脏页,innoDB存储引擎中可能发生如下几种Fuzzy Checkpoint:

  • Master Thread Checkpoint
  • FLUSH_LRU_LIST Checkpoint
  • Async/Sync Flush Checkpoint
  • Dirty Page too much Checkpoint

Master Thread Checkpoint :Master Thread 每秒或者每十秒的速度从缓冲池的脏页列表刷新一定比例的页回磁盘,这个过程是异步,不影响查询线程

FLUSH_LRU_LIST Checkpoint:InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用,在InnoDB1.1.x版本之前,需要检查LRU列表中是否有足够可用空间操作发生在查询线程,如果没有100个可用页,则InnoDB存储引擎会将LRU列表尾端页移除,如果这些页有脏页,则需要Checkpoint,该操作称FLUSH_LRU_LIST Checkpoint,从MySQL5.6版本之后,这个检查被放在单独的Page Cleaner线程中进行,且用户通过参数innodb_lru_scan_depth控制LRU列表中可用页数量,默认是1024,通过命令show variables like 'innodb_lru_scan_depth'查看

Async/Sync Flush Checkpoint:指redo log不可用情况,这时需要强制将一些页刷新回磁盘,此时脏页从脏页列表选取的,为了保证redo log的重复可用

Dirty Page too much Checkpoint:脏页数量太多,导致innoDB强制进行Checkpoint,保证了缓冲池的可用,由参数innodb_max_dirty_pag_pcts控制,默认是75,表示脏页超过75%将刷新一部分到磁盘

InnoDB的关键特性

InnoDB的关键特性包括:

  • 插入缓冲(Insert Buffer)
  • 两次写(Double Write)
  • 自适应哈希索引(Adaptive Hash Index)
  • 异步IO(Asycn IO)
  • 刷新邻接页(Flush Neighbor Page)

插入缓冲

Insert Buffer

在InnoDB存储引擎中,主键是行唯一的标识符。插入聚集索引(Primary Key)一般是顺序排列,速度非常快(如果主键是UUID则插入数据和辅助索引一样,不是连续的),而对于非聚集索引叶子节点的插入不再是顺序的(B+树非聚集索引的离散性),导致插入效率降低。

InnoDB存储引擎开创性地设计了Insert Buffer,对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中,然后再以一定的频率和情况进行Insert Buffer 和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。

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

  • 索引是辅助索引
  • 索引不是唯一索引

当满足以上两个条件时,InnoDB存储引擎会使用Insert Buffer来提高插入操作的性能了。要求辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去查找肯定又会有离散读取的情况发生,从而导致Insert Buffer失去了意义。Insert Buffer存在问题:在写密集的情况下,插入缓冲会占用过多的缓冲池内存(innodb_buffer_pool),默认最大可以占用到1/2的缓冲池内存。

Change Buffer

InnoDB从1.0.x版本开始引入了Change Buffer,可将其视为Insert Buffer的升级。从这个版本开始,InnoDB存储引擎可以对DML操作:INSERT、DELETE、UPDATE都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge buffer。当然和之前Insert Buffer一样,Change Buffer适用的对象依然是非唯一的辅助索引。对用到非唯一二级索引的删除和更新操作,当缓冲池不存在时,这些操作会被缓冲到Change Buffer上,等MySQL空闲了、或者是MySQL关闭前、或者是有读取操作时再将这部分缓存操作merge到B+Tree中。

InnoDB存储引擎提供了参数innodb_change_buffering用来开启各种Buffer的选项。该参数可选的值为:inserts、deletes、purges、changes、all、none,inserts、deletes、purges就是前面讨论对应的插入、删除、更新操作。changes 表示启用inserts 和deletes,all表示启用所有,none表示都不启用。默认值为all.
从InnoDB 1.2.x版本开始,可以通过参数innodb_change_buffer_max_size来控制Change Buffer 最大使用内存的数量,默认是25表示最多使用1/4的缓冲池内存空间,可配置的最大有效值为50

Insert Buffer内部实现

Insert Buffer的数据结构是一棵B+树。在MySQL 4.1之前的版本中每张表有一棵Insert Buffer B+树。而在现在的版本中,全局只有一棵 Insert Buffer B+树,负责对所有的表的辅助索引进行Insert Buffer,并且这棵B+树存放在共享表空间中,默认也就是ibdatal中。因此,试图通过独立表空间ibd文件恢复表中数据时,往往会导致CHECK TABLE失败。这是因为表的辅助索引中的数据可能还在Insert Buffer中,也就是共享表空间中,所以通ibd文件进行恢复后,还需要进行REPAIR TABLE操作来重建表上所有的辅助索引。Insert Buffer是一个B+树,非叶子节点存放数据,该数据是查询的search key(键值),构造如下:

MySQL的InnoDB存储引擎_第11张图片

search key一共占用9个字节,分别是:

  • space:插入路所在表的表空间id,在InnoDB中每个表都有唯一的space id,占用4个字节
  • marker:兼容老版本Insert Buffer,占用1个字节
  • offset:表示页所在偏移量,占用4个字节

当一个辅助索引要插入到页(space,offset)时,如果这个页不在缓冲区,则按照上述构造search key,查询Insert Buffer这个B+树,在按照特定的规则插入到B+树上。

Merge Insert Buffer

Insert Buffer中的记录何时合并(merge)到真正的辅助索引中呢?概括地说,Merge Insert Buffer的操作可能发生在以下几种情况下:

  • 辅助索引页被读取到缓冲池时;
  • Insert Buffer Bitmap 页追踪到该辅助索引页已无可用空间时;
  • Master Thread.

辅助索引页被读取到缓冲池中时,例如当执行SELECT查询操作,这时需要检查 Insert Buffer Bitmap页,确认该辅助索引页是否有记录存放于Insert Buffer B+树中。若有,则将 B+树中该页的记录插入到该辅助索引页中。可以看出对该页多次的记录操作通过一次操作合并到了原有的辅助索引页中,因此性能会有大幅提高。

Insert Buffer Bitmap 页用来追踪每个辅助索引页的可用空间,并至少有1/32页的空间。若插入辅助索引记录时检测到插入记录后可用空间会小于1/32页,则会强制进行一个合并操作,即强制读取辅助索引页,将Insert Buffer B+树中该页的记录及待插入的记录插人到辅助索引页中。

在Master Thread 线程中每秒或每10秒会进行一次Merge Insert Buffer的操作,不同之处在于每次进行merge操作的页的数量不同。

两次写

Insert Buffer 用于提升InnoDB存储引擎的性能,doublewrite(两次写)是为了提升InnoDB存储引擎数据页的可靠性。

当发生数据库宕机时,插入操作的某个页只写了一部分,比如16KB的页,只写了前4KB,这种情况被称为部分写失效(partial page write),未使用doublewrite技术前,可能会导致数据丢失的情况。(就算通过重做日志进行恢复。但是重做日志中记录的是对页的物理操作,如偏移量800,写'aaaa'记录。如果这个页本身已经发生了损坏,再对其进行重做是没有意义的)。在InnoDB存储引擎中,double write的体系架构如下图:

MySQL的InnoDB存储引擎_第12张图片

doublewrite 由两部分组成:

  • 一部分是内存中的doublewrite buffer,大小为2MB
  • 另一部分是物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB

double write步骤

  1. 在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy 函数将脏页先复制到内存中的 doublewrite buffer,
  2. doublewrite buffer再分两次,每次1MB顺序地写人共享表空间的物理磁盘上
  3. 然后调用fsync函数,同步磁盘,在这个过程中,因为doublewrite页是连续的,因此这个过程是顺序写的,开销不大。
  4. 完成doublewrite页的写入后,在将doublewrite buffer中的页写入各个表空间文件中。

可以通过如下命令查看:

MySQL的InnoDB存储引擎_第13张图片

可以看出double write一共写了5681个页,但是实际写入次数只有1682次,可以通过在高峰中Innodb_dblwr_pages_written:Innodb_dblwr_writes的比例是否小于64:1,如果远远小于说明系统写入压力不高。

自适应哈希索引

哈希(hash)是一种非常快的查找方法,在一般情况下这种查找的时间复杂度为O(1),而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为3~4层,故需要3~4次的查询。

InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(Adaptive Hash Index,AHI),AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表构建哈希索引。InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

AHI的要求

即对这个页的连续访问模式必须是一样的。也就是对查询条件是一样的,例如对于(a,b)这样的联合索引页,其访问模式可以是以下情况:

  • WHERE a=xxx
  • WHERE a=xxx and b=xxx

但是若交替进行上述两种查询,那么InonDB存储引擎不会对该页构造AHI,此外AHI还有如下的要求:

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

此外需要知道,希索引只能用来搜索等值的查询,如SELECT*FROM table WHERE index_col='xxx',而对于其他查找类型,如范围查找,是不能使用哈希索引的。根据InnoDB存储引擎官方的文档显示,启用AHI后,读取和写入速度可以提高2倍,辅助索引的连接操作性能可以提高5倍。毫无疑问,AHI是非常好的优化模式,其设计思想是数据库自优化的(self-tuning),无需DBA对数据库进行人为调整。

通过命令 SHOW ENGINE INNODB STATUS可以看到当前AHI的使用状况:也可以禁用该特性,默认AHI是开启的

异步IO

为了提高磁盘操作性能,当前的数据库系统都采用异步IO(Asynchronous IO,AIO)的方式来处理磁盘操作。InnoDB存储引擎也是如此。所以的异步IO就是用户可以在发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有IO操作的完成,这就是AIO

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

InnoDB 1.1.x开始(InnoDB Plugin不支持),提供了内核级别AIO的支持,称为Native AIO,用户可以通过开启和关闭Native AIO功能来比较InnoDB性能的提升。官方的测试显示,启用Native AIO,恢复速度可以提高75%,在InnoDB存储引擎中,read ahead方式的读取都是通过AIO完成,脏页的刷新,即磁盘的写入操作则全部由AIO完成。


刷新邻接页

InnoDB存储引擎还提供了Flush Neighbor Page(刷新邻接页)的特性,其工作原理为:当刷新一个脏页时,InnoDB存储引擎会检测该页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。
InnoDB存储引擎从1.2.x版本开始提供了参数innodb_flush_neighbors,用来控制是否启用该特性。对于传统机械硬盘建议启用该特性,而对于固态硬盘有着超高IOPS性能的磁盘,则建议将该参数设置为0,即关闭此特性。

InnoDB的启动关闭恢复

innodb_fast_shutdown参数

MySQL实例关闭时,参数 innodb_fast_shutdown影响着InnoDB存储引擎表的行为。该参数可取值为0、1、2,默认值为1

  • 0表示在MySQL数据库关闭时,InnoDB需要完成所有的full purge 和 merge insert buffer,并且将所有的脏页刷新回磁盘。这需要一些时间,有时甚至需要几个小时来完成。如果在进行InnoDB升级时,必须将这个参数调为0,然后再关闭数据库。
  • 1是参数innodb_fast_shutdown的默认值,表示不需要完成上述的 full purge和merge insert buffer操作,但是在缓冲池中的一些数据脏页还是会刷新回磁盘。
  • 2表示不完成 full purge 和 merge insert buffer操作,也不将缓冲池中的数据脏页写回磁盘,而是将日志都写入日志文件。这样不会有任何事务的丢失,但是下次MySQL 数据库启动时,会进行恢复操作(recovery).

如果没有正常地关闭数据库,如用kill命令关闭数据库,或在MySQL数据库运行中重启了服务器,或在关闭数据库时,将参数innodb_fast_shutdown设为了2时,下次MySQL数据库启动时都会对InnoDB存储引擎的表进行恢复操作。

innodb_force_recovery参数

参数 innodb_force_recovery影响了整个 InnoDB存储引擎恢复的状况。默认为0,代表当发生需要恢复时,进行所有的恢复操作,当不能进行有效恢复时,如数据页发生了corruption,MySQL数据库可能发生宕机(crash),并把错误写入错误日志中去,但是在某些情况下,可能并不需要进行完整的恢复操作,因为用户自己知道怎么进行恢复(比如从备份数据重新导入),这时可以设置成其他的6个非零值:1~6,大的数字表示包含了前面所有小数字表示的影响。具体情况如下:

  • 1(SRV_FORCE_IGNORE_CORRUPT):忽略检查到的corrupt页。
  • 2(SRV_FORCE_NO_BACKGROUND):阻止Master Thread线程的运行,如Master Thread 线程需要进行 full purge操作,而这会导致crash.
  • 3(SRV_FORCE_NO_TRX_UNDO):不进行事务的回滚操作。
  • 4(SRV_FORCE_NO_IBUF_MERGE):不进行插入缓冲的合并操作。
  • 5(SRV_FORCE_NO_UNDO_LOG_SCAN):不查看撤销日志(Undo Log),InnoDB存储引擎会将未提交的事务视为已提交。
  • 6(SRV_FORCE_NO_LOG_REDO):不进行前滚的操作。

需要注意的是,在设置了参数innodb_force_recovery大于0后,用户可以对表进行select、create 和 drop操作,但 insert、update 和 delete 这类DML操作是不允许的。

你可能感兴趣的:(MySQL,mysql)