InnoDB存储引擎--InnoDB存储引擎


​  InnoDB是事务安全的MySQL存储引擎,设计上采用了类似与Oracle数据库的架构.通常来说,InnoDB存储引擎是OLTP应用中核心表的首选存储引擎.

1. InnoDB存储引擎概述

​  该存储引擎是第一个完整支持ACID事务的MySQL存储引擎,其特点是行锁设计,支持MVCC,支持外键,提供一致性非读定锁,同时被设计用来最有效地利用以及使用内存和CPU.

2. InnoDB存储引擎的版本

  1. 老版本InnoDB:支持ACID,行锁,MVCC
  2. InnoDB 1.0.x:增加了compress和dynamic页格式
  3. InnoDB 1.1.x:增加了Linux AIO,多回滚段
  4. InnoDB 1.2.x:增加了全文索引支持,在线索引添加

3. InnoDB体系架构

​  InnoDB存储引擎内部有多个内存块,可以认为这些内存块组成了一个大的内存池

在这里插入图片描述

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

(1). 后台线程

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

1). Master Thread

​  主要将缓冲池中的数据异步刷新到磁盘,保证数据的一致性.

2). IO Thread

​  InnoDB存储引擎中大量使用了AIO来处理写IO请求,这样可以极大提高数据库的性能,而IO Thread的工作只要是负责这些IO请求的回调处理.

3). Purge Thread

​  回收已经使用并分配的undo页.

​  (undo:在操作数据前,现将数据备份到一个地方然后修改,这个备份叫undo log)

4). Page Cleaner Thread

​  脏页的刷新操作都放入到单独的线程来完成的.

(2). 内存

1). 缓冲池

​  InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理.因此可将其视为基于磁盘的数据库系统.基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能

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

​  对数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上.通过Checkpoint的机制刷新回磁盘

​  缓冲池的大小直接影响着数据库的整体性能.

​  缓冲池的配置通过参数innodb_buffer_pool_size来设置

​  具体来看,缓冲池中缓存的页类型有:索引页,数据页,undo页,插入缓冲,自适应哈希索引,InnoDB存储的锁信息,数据字典等.

在这里插入图片描述
 从InnoDB1.0.x版本开始,允许有多个缓冲池实例.每个页根据哈希值平均分配到不同的缓冲池实例中,可通过参数innodb_buffer_pool_instances来配置缓冲池实例,默认为1

​  可以通过命令SHOW ENGINE INNODB STATUS来观察每个缓冲池实例对象运行的状态

2). LRU list,Free list和Flush list

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

​  缓冲池中页的默认大小为16K.

​  InnoDB存储引擎还对传统的LRU算法进行了优化,加入了midpoint位置.

​  新读取的页,虽然是最新访问的页,但是并不一定是经常活跃的页,直接放入列表前端有些不妥.InnoDB存储引擎的优化为将新页插入到设定好的midpoint位置.默认为列表长度的前5/8处.InnoDB存储引擎将midpoint前的表成为new列表,之后的页称之为old列表.(还有一个原因是,如果进行的是扫描操作,那么所有的页都会进入LRU列表一次,插入前端会刷新整个LRU列表)

​  InnoDB存储引擎还提供了innodb_old_blocks_time参数,用于设定读取页后多久将该页放入LRU列表的热端.

​  刚启动数据库时,LRU列表是空的,所有的页都存放在Free列表中.当需要从缓冲池分配页时,首先从Free列表中查找是否有可用的空闲页,如果有将该空闲页从Free列表中删除,并插入到LRU列表中.否则从LRU列表尾端淘汰一个页,重新分配内存.

​  LRU列表中的页被修改后,被称为脏页.而Flush列表中的页即为脏页.需要注意的是,脏页既存在于Flush列表中,页存在于LRU列表中.

​  可以通过 SHOW ENGINE INNOB STATUS 来观察LRU列表以及Free列表的使用情况和运行状态,具体可参考mysql之show engine innodb status解读或者参考MySQL技术内幕InnoDB存储引擎第二版P27

​  InnoDB从1.0x版本开始支持压缩页功能,即将原本16KB的页进行压缩。对于非16KB的页,通过unzip_LRU进行管理,其次通过伙伴算法进行内存的分配。例如对需要从缓冲池中申请页为4KB的大小,过程如下

  1. 检查4KB的unzip_LRU列表,检查是否有可用空闲页
  2. 若有,则直接使用
  3. 否则,检查8KB的unzip_LRU列表
  4. 如能够得到空闲页,将页分成2个4KB页,存放到4KB的unzip_LRU列表
  5. 若不能得到空闲页,从LRU列表中申请一个16KB的页,将页分为1个8KB的页和2个4KB的页,分别存放到对应的unzip_LRU列表

​  LRU列表用于管理缓冲池中页的可用性,Flush列表用来管理将页刷新会磁盘,二者互不影响.同理,Flush列表也可以通过命令SHOW ENGINE INNODB STATUS查看。

3). 重做日志缓冲

​  InnoDB存储引擎首先将重做日志先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件,这个缓冲一般不会设置的很大,因为一般情况下每一秒都会有刷新操作.由参数innodb_log_buffer_size控制,默认大小为8MB.

​ 有三种情况会将重做日志缓冲中的内容刷新到外部磁盘的重做日志中

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

4). 额外的缓冲池

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

4. Checkpoint技术

​  为了避免发生数据丢失的问题,当前的事务数据库系统普遍都采用了Write Ahead Log策略,即当事务提交时,先重写日志,再修改页.当由于发生宕机而导致数据丢失时,通过重做日志来完成数据恢复。

​  那么试想,是不是可以只保存日志,完全通过日志来恢复整个数据库系统中的数据带宕机发生的时刻,这需要两个前提条件:

  1. 缓冲池可以缓存数据库中所有数据
  2. 重做日志可以无线增大

​  如果上面两个条件都满足,还需要考虑宕机后数据恢复的时间.因此Checkpoint技术的目的主要是解决

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

​  所以,出现了Checkpoint技术,当数据库发生宕机时,不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘了.当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,如果此页为脏页,会强制执行Checkpoint,将脏页刷新回磁盘.

​  对于InnoDB存储引擎而言,是通过LSN来标记版本的.而LSN是8字节的数据,其单位是字节.

在InnoDB存储引擎内部,有两种Checkpoint:

  • Sharp Checkpoint:刷新全部脏页
  • Fuzzy Checkpoint:在InnoDB内部使用,只刷新部分脏页

发生Fuzzy Checkpoint的几种情况:

  1. Master Thread Checkpoint:在Master Thread中定时发生的.
  2. FLUSH_LRU_LIST Checkpoint:为了保证LRU列表中有差不多100个空闲页,当移除尾端页时,会进行Checkpoint.
  3. Async/Sync Flush Checkpoint:重做日志不可用时,会将一些页强制刷新回磁盘.
  4. Dirty Page too much Checkpoint:脏页数量太多,会发生Checkpoint

5. Master Thread的工作方式

(1). InnoDB 1.0.x版本之前

​  内部由多个循环组成:主循环(loop),后台循环(backgroup loop),刷新循环(flush loop),暂停循环(suspend loop).Master Thread会根据数据库的状态在这些循环之间进行切换.

​  Loop被称为主循环,其中有两大操作-----每十秒的操作和每秒都进行的.

​  loop循环通过thread sleep实现,这意味这这些操作之间的计时是不准确的.

每一秒进行的操作:

  • 日志缓冲刷新到磁盘,即时还没有提交事务
  • 可能会合并插入缓冲(通过判断前一秒发生IO的次数判断IO压力是否小,从而决定是否进行合并插入缓冲)
  • 可能会刷新脏页到磁盘(脏页比例超过参数设置的值,会进行刷新,默认值为90%)
  • 如果当前没有用户活动,可能会切换到backgroup loop

每十秒进行的操作:

  • 可能会刷新100个脏页(判断过去十秒内IO次数是否小于200次)
  • 合并最多5个插入缓冲
  • 将日志缓冲刷新到磁盘
  • 删除无用的undo页
  • 刷新100或者10个脏页到磁盘

​ 如果当前没有用户活动或者数据库关闭,就会切换到background loop,在这个循环内,会执行以下操作:

  • 删除无用的Undo页
  • 合并20个插入循环
  • 跳回到主循环
  • 也可能会调到flush loop中不断刷新100个页直到符合条件

(2). InnoDB 1.2.x版本之前

​  在之前的版本中,由于硬编码,即时每秒内有很多的页写入和多个插入缓冲的合并,Master Thread只会选择刷新其中的100个脏页和20个插入缓冲,当发生宕机时,会积累很多的数据没有刷新回磁盘,导致恢复的时间会需要很久.

​  从InnoDB 1.0.x版本开始,提供了innodb_io_capacity参数来表示磁盘IO吞吐量,默认为200.对于刷新到磁盘的数量,会按照该参数的百分比进行控制:

  • 合并插入缓冲时,每次合并数量为该参数的5%.
  • 刷新脏页时,刷新的数量为该参数的值.

​  从InnoDB 1.0.x版本开始,刷新脏页时的脏页占比参数innodb_max_dirty_pages_pct默认值由90变为了75.加快了脏页的刷新频率.

​  引入了参数来判断需要刷新脏页的最适合的数量,其内部方法通过判断重做日志的速度来判断最适合的刷新脏页数量.

​  引入了控制每次full purge()回收Undo页的数量的参数,默认为20.

​  InnoDB还对主循环内部进行了优化,使压力大时不一定总是等待1秒.

(3). InnoDB1.2.x版本

​  对于脏页的刷新操作,从Master Thread线程分离到一个单独的Page Cleaner Thread进行.

6. InnoDB关键特性

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

(1). 插入缓冲

1). Insert Buffer

​  Insert Buffer和数据也一样,也是物理页的一部分,并不是缓冲池的组成.

​  在InnoDB存储引擎中,主键是行唯一的标识符,通常应用程序中行记录的插入都是按照主键递增的顺序插入的.

​  但是对于非聚集索引,插入的顺序是离散的.(聚集索引是指数据航的物理顺序与列值的逻辑顺序顺序相同,一张表中只能有一个索引)

​  InnoDB存储引擎开创性的设计了Insert Buffer,对于非聚集索引的插入或更新操作,不是每一次的直接插入到索引页中,而是先判断插入的非聚集索引是否在缓冲池中,如果在直接插入(这样是直接操作内存的,很快),如果不在,先放入一个Insert Buffer对象中.然后在以一定的频率将多个插入合并到一个操作中.

Insert Buffer的使用必须满足一下两个条件:

  1. 索引是辅助索引:叶子节点中存储主键值,每次查找时,现根据索引找到叶子节点中的主键值,才从聚集索引中得到完整记录.
  2. 索引不是唯一的:如果唯一,每一次插入会先查找判重复,违背了Insert Buffer减少操作索引次数的目的.

​  但是,Insert Buffer存在一个问题,在写密集的情况下,插入缓冲会占用过多的缓冲池内存(虽然保存在物理磁盘,但是缓冲池中还是有这部分信息的)

2). Change Buffer

​  从InnoDB 1.0.x版本开始引入了Change Buffer,可将其视为Insert Buffer的升级.从这个版本开始,可以对DML操作–insert,delete,update都进行缓冲.

3). Insert Buffer的内部实现

​  Insert Buffer的数据结构是一颗B+树,非叶节点存放的是查询的search key(键值).

search key占9字节:

  • space:4字节,表示待插入记录所在表的表空间id.
  • marker:1字节,用来兼容老版本的Insert Buffer.
  • offset:4字节,表示页所在的偏移量.

叶节点存储的数据结构如下:

  • 前三个字段和search key一样,共占9字节.
  • metadata:4字节,又分为以下三个部分:
    • IBUF_REC_OFFSET_COUNT:2字节,表示进入Insert Buffer的顺序.
    • IBUF_REC_OFFSET_TYPE:1字节
    • IBUF_REC_OFFSET_FLAGS:1字节
  • secondary index record:实际插入的值.

​  为了保证每次合并插入缓冲都必须成功,必须要有一个特殊的页来标记每个辅助索引页的可用空间,类型为Insert Buffer Bitmap.

​  每个Insert Buffer Bitmap页用来追踪16384个辅助索引页,也就是256个区.每一个Insert Buffer Bitmap都会紧跟在这16384个页后面.

4). Merge Insert Buffer

Merge Insert Buffer(合并)操作发生在以下几种情况下:

  • 辅助索引被读取到缓冲池
  • Insert Buffer Bitmap页追踪到该辅助索引页没有可用空间(剩余空间不足1/32页)
  • Master Thread(主循环定时合并),每次只合并一部分页,由InnoDB存储引擎在Insert Buffer B+树中随机读取

(2). 两次写

​  如果在InnoDB存储引擎写入一个页的过程中,发生了宕机,此时这个页被写了一半,已经与前一次Checkpoint后的磁盘不一致了,使用重做日志是无法恢复的.

​  doublewrite由两部分组成,一部分是内存中的doublewrite buffer,大小为2MB,另一部分是物理磁盘上共享表空间中的128个页(两个区),大小也为2MB.

​  在对脏页进行刷新时,会先将脏页先复制到内存中的doublewrite buffer,然后同步磁盘,这个操作非常快.完成之后在将doublewrite buffer中的页写入到各个表空间中.相当于在本地磁盘中先快速写入一遍,作为副本,这个副本和本地磁盘中的数据总有一个是干净的

在这里插入图片描述

(3). 自适应哈希索引

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

​  有一个要求就是对这个页的连续访问必须使用相同的查询条件.

​  哈希索引只能用来搜索等值的查询.对于范围查找,是不能使用哈希索引的.

(4). 异步IO

​  为了提高磁盘操作性能,当前的数据库系统都采用异步IO(AIO)的方式来处理磁盘操作.与之相对应的是Sync IO,即没进行一次IO操作,需要等待此次操作结束才嫩而过继续接下来的操作.

​  异步IO就是指用户可以在发出一个IO请求后立即在发出另一个IO请求,全部的IO请求发出后,等待所有的IO操作完成,这就是AIO.

​  另一个优势,是可以进行IO Merge操作,也就是将多个IO合并为一个IO请求,从而提高IOPS性能.

(5). 刷新邻接页

​  当刷新一个脏页时,InnoDB存储引擎会尖刺该页所在的区的所有页,如果是脏页,那么一起进行刷新.

​  对于固态硬盘,建议关闭此特性.

7. 启动,关闭与修复

​  关闭数据库时,参数innodb_fast_shutdown影响着存储引擎为InnoDB的表的行为.

innodb_fast_shutdown参数含义:

  • 0:MySQL关闭时,InnoDB需要完成所有的无用Undo页清除和合并插入缓存,并且刷新所有脏页会磁盘.
  • 1:只刷新脏页.
  • 2:将日志写入日志文件,下次MySQL数据库启动时进行恢复.

​  参数innodb_force_recovery影响了整个InnoDB存储引擎恢复的状况.该参数默认为0,表示当需要恢复时,进行所有的恢复操作,当不能进行有效恢复(比如发生了数据库坏块),MySQL数据库可能发生宕机,并将错误记录到错误日志中.还有6个可以设置的值来屏蔽一些错误.

  • 1(SRV_FORCE_IGNORE_CORRUPT):忽略检查到的corrupt页
  • 2(SRV_FORCE_NO_BACKGROUND):阻止主线程的运行,如主线程需要执行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时,insert,update和delete这类DML操作都是不允许的.

发布了141 篇原创文章 · 获赞 47 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41596568/article/details/104332210