一、MySQL 存储引擎-InnoDB

102 阅读9分钟

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

一、InnoDB 存储引擎特点

 支持事务,其特点是行锁设计、支持外键,并支持非锁定读(默认读取操作不会产生锁),从 5.5.8 版本后作为默认的存储引擎。
 InnoDB 通过使用多版本并发控制(MVCC)来获得高并发性,并且实现了SQL标准的4种隔离级别,默认为 REAPEATABLE 级别。同时,使用 next-key locking 的策略来避免幻读的产生。
 InnoDB 还提供了插入缓冲(insert buffer)、两次写(double write)、自适应哈希索引(adaptive hash)、预读(read ahead)等高性能和高可用的功能。

 对于表中数据的存储,InnoDB采用了聚集(clustered)的方式,每张表的存储都是按主键的顺序进行存放,如果没有显示地在表定义时指定主键,则会生成一个6字节的ROWID以作为主键。

二、InnoDB 存储引擎

2.1 InnoDB 体系架构

2.1.1 后台线程

 作用:刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。并将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下 InnoDB 能恢复到正常运行状态。
 特点:多线程的模型

Master Thread

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

IO Thread

  InnoDB中大量使用了AIO(Async IO)来处理 IO 请求,这样可以极大提高数据库的性能。IO Thread主要是负责这些 IO 请求的回调处理。
  共有4种 IO Thread: write、read、insert buffer、log IO Thread。

Purge Thread

  事务被提交后,其所使用的 undolog 可能不再需要,需要该线程回收已经使用并分配的 undo 页。1.2 版本后支持多个 Purge Thread,目的是加快 undo 页的回收。

Page Cleaner Thread

  1.2 版本后从 Master Thread 中分离出来的线程(减轻工作及对用户查询线程的阻塞),用于脏页的刷新。

2.1.2 内存
缓冲池

  InnoDB 是基于磁盘存储的,将记录按照页的方式管理,因此可视其为基于磁盘的数据库系统。在数据库系统中,由于 CPU 速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池来提高数据库的性能。缓冲池的大小直接影响数据库的整体性能。
  缓冲池是一块内存区域,通过内存的速度弥补磁盘速度较慢的影响。
    - 读取页:首先从磁盘读到的页存在缓冲池中,下次再读取相同的页时,先判断是否在缓冲池中,如果在则直接读取该页,否则读取磁盘上的页。
    - 修改操作:先修改缓冲池中的页,再以一定频率刷新到磁盘上。(非立即触发,通过Checkpoint机制)

LRU List、Free List 和 Flush List

  数据库中的缓冲池是通过 LRU 来进行管理的。即最常用的在 LRU 列表的前端,最近最少使用的在列表尾端。当缓冲池满时首先释放尾端页。
-优化:InnoDB 在 LRU 列表中加入了 midpoint 位置。新读到的页放入 midpoint 位置而不是首部,默认在 5/8 处,前部分为热点数据。这样做是为了避免新数据将热点数据挤出列表。
脏页:LRU 中的页被修改后即成为脏页,与磁盘上的页数据不一致,这时会通过 CHECKPOINT 机制将脏页刷新回磁盘,Flush 列表中的页即为脏页列表。

重做日志缓冲 (redo log buffer)

  先将重做日志信息先放入这个缓冲区,以一定频率将其刷新到重做日志文件。通常每秒刷新到日志文件因此该缓冲一般不需要设置很大。
  重做日志刷新磁盘的情况:

  • Master Thread 每秒刷新
  • 事务提交
  • redo log buffer 剩余空间小于 1/2
额外内存池

  在 InnoDB 存储引擎中,堆内存的管理是通过内存堆(heap)的方式进行的。在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域内存不够会从缓冲池中申请。该区域会记录一些诸如 LRU、锁、等待等信息。

2.2 Checkpoint 技术

  由于缓冲池是存于内存的,因此发生宕机会造成数据丢失,并且每次页发生变化就需要将页刷新到磁盘,开销很大。
  为避免数据丢失,事务数据库系统普遍采用Write Ahead Log 策略,当数据提交时,先写重做日志,再修改页。发生宕机则通过重做日志来修复数据。
  用于解决以下问题:

  • 缩短数据库恢复时间
  • 缓冲池不够用时将脏页刷新到磁盘
  • 重做日志不可用时刷新脏页   数据库宕机后,不需要重做所有日志,在 CheckPoint 之前的页都已经刷回磁盘,只需对 CheckPoint 后的日志进行恢复,大大缩短恢复时间。
      此外,当缓冲池不够用时,LRU 会溢出最近最少使用的页,若此页为脏页,那么需要强制执行 CheckPoint,将脏页刷回磁盘。
    在 InnoDB 存储引擎内部,有两种 CheckPoint:
  • Sharp Checkpoint 发生在数据库关闭时,将所有脏页刷回磁盘
  • Fuzzy Checkpoint 数据库运行时使用

2.3 InnoDB 关键特性

  • 插入缓冲 (Insert Buffer)
  • 两次写 (Double Write)
  • 自适应哈希索引 (Adaptive Have Index)
  • 异步IO (Async IO)
  • 刷新临近页 (Flush Neighbor Page)
2.3.1 插入缓冲 - 性能提升
Insert Buffer

  插入缓冲与数据页一样,也是物理页的一个组成部分。
  对于主键索引,通常我们是顺序插入的,不需要磁盘的随机读取,但对于非聚集索引,在插入时,数据页的存放还是按主键进行顺序存放,但叶子节点的插入不再是顺序的了,这时就需要离散地访问非聚集索引页,由于随机读取的存在导致了插入操作性能下降。
  因此产生了 Insert Buffer, 对于非聚集索引的插入或更新操作,不是每次直接插入到索引页,而是先判断插入的非聚集索引页是否在缓冲池中,在则直接插入;不在则先放到一个 Insert Buffer 对象中,然后再以一定频率和情况进行 Insert Buffer 和 辅助索引页子节点的 merge 操作,通常能将多个插入合并到一个操作中,大大提高插入性能。

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

  • 索引是辅助索引
  • 索引不是唯一的:防止去查找索引页来判断记录唯一性,又会有离散读取的情况。
Change Buffer

  可视为 Insert Buffer 的升级,还有 Delete Buffer、Purge Buffer.
  对一条记录进行 UPDATE 可能分为两个过程:

  • 将记录标记为已删除 -Delete Buffer
  • 真正将记录删除 -Purge Buffer
2.3.2 两次写 - 数据页的可靠性

  当存储引擎正在写入某个页到表中,而只写了一部分便发生了宕机,这种情况即部分写失效,doublewrite 就是用来解决这种情况的。
  通常发生写失效时可以使用重做日志(redo log)进行恢复,但重做日志中记录的是对页的物理操作,可能该页本身已经发生了损坏,因此无法恢复,所以在应用重做日志前还需要一个副本,当写入失效发生时,先通过页的副本还原该页,再进行重做,这就是 doublewrite。
doublewrite 由两部分组成:

  • 内存中的 doublewrite buffer, 大小为2MB
  • 物理磁盘上共享表空间中连续的128个页,即2个区   1. 在对缓冲池的脏页进行刷新时,不直接写磁盘,而是通过memcpy函数将脏页先复制到内存中的 doublewrite buffer,
      2. 之后通过 doublewrite buffer 再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,
      3. 然后马上调用 fsync 函数,同步磁盘,避免缓冲写带来的问题。
      4. 在完成 doublewrite 页的写入后,再将 doublewrite buffer 中的页写入各个表空间文件中,此时的写入则是离散的。

image.png   当将页写入磁盘的过程中发生了崩溃,在恢复过程中,可以从共享表空间中的 doublewrite 中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。

2.3.3 自适应哈希索引

  InnoDB 会监控对表上各索引页的查询,如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(AHI)。
  要求:对这个页的连续访问模式(查询条件)必须是一样的,并且以该模式访问了100次、页通过该模式访问了 页中记录 * 1/16 次。
  哈希索引只能用来搜索等值的查询。

2.3.4 异步IO

  为提高磁盘操作性能,当前的数据库系统都采用异步 IO(AIO) 的方式来处理磁盘操作。
  用户可以在发出一个IO请求后立即再发出另一个请求,当全部IO请求发送完毕后,等待所有IO操作的完成,这就是AIO。
  另一个优势是可以进行IO Merge 操作(将多个IO合并为一个),可以提高IOPS的性能。

2.3.5 刷新邻接页

  当刷新一个脏页时,会检测该页所在区的所有页,如果是脏页则一起刷新,这样做的好处是通过AIO可以将多个IO写入操作合并为一个IO操作,因此在机械硬盘下有着显著优势,而对于固态硬盘有着超高IOPS性能的磁盘,则可以关闭该特性。