mysql技术内幕-InnoDB存储引擎
发表于:2025-03-11 | 分类: 学习

InnoDB体系架构

从图中可见,InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个巨大的内存池,负责如下工作:

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

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

后台线程

  1. Master Thread
    这是一个非常核心的后台线程,具有最高优先级别。主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、UNDO页的回收等。
  2. IO Thread
    InnoDB存储引擎中大量使用了AIO(Async IO)来处理写IO请求,这样可以极大的提高数据库的性能,而IO Thread的工作主要负责这些IO请求的回调处理。
  3. Purge Thread
    事务被提交后,其所使用的undolog可能不再需要,因此需要Purge Thread来回收已经使用并分配的undo页。
  4. Page Cleaner Thread
    其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。其目的是减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB的性能。

内存

  1. 缓冲池
    缓冲池简单点来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库的影响。缓冲池的大小直接影响数据库的整体性能。具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等。不能简单地认为,缓冲池只是缓存索引页和数据页,他们只是占缓冲池很大的一部分而已。内存结构如下图所示:

  2. LRU List、 Free List和Flush List
    通常来说,数据库中的缓冲池是通过LRU(Latest Recent Used, 最近最少使用)算法来进行管理。即最频繁使用的页在LRU列表的前端,最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。
    LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何的页。这时页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放入到LRU列表中。否则根据LRU算法,淘汰LRU列末尾的页,将该内存分配给新的页。
    LRU列表中的页被修改后,该页被称为脏页(dirty page),即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过CHECKPOINT机制将脏页刷新回磁盘,而Flush列表中的页即为脏页列表。

    脏页即存在于LRU列表中,也存在于Flush列表中。LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新回磁盘,二者互不影响。

  3. 重做日志缓冲
    InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将重做日志信息先放入这个缓冲区,然后按一定频率将其刷新到重做日志文件。

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

Checkpoint技术

Checkpoint(检查点)技术用于解决以下问题:

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

当数据库发生宕机时,数据库不需要重做所有日志,因为Checkpoint之前的页都已经刷回磁盘。所以数据库只需要把Checkpoint后的重做日志进行恢复。这样大大缩短了恢复的时间。

在innoDB存储引擎内部,有两种Checkpoint,分别为:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

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

但是若数据库在运行时也使用Sharp Checkpoint,那么数据库的可用性就会受到很大的影响。所以在innoDB引擎内部使用Fuzzy Checkpoint进行页的刷新,即只刷新一部分脏页,而不刷新所有的脏页回磁盘。

innoDB存储引擎可能发生如下几种情况的Fuzzy Checkpoint:

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

Master Thread工作方式

在innoDB 1.0x版本之前的Master Thread具有最高的线程优先级别。其内部由多个循环(loop)组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。Master Thread回根据数据库运行的状态在loop、background loop、flush loop和suspend loop中进行切换。

InnoDB关键特性

InnoDB存储引擎的关键特性包括:

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

这些特性为InnoDB带来更好的性能以及更高的可靠性。

  1. 插入缓冲

    • Insert Buffer
      innoDB存储引擎中,主键是行唯一的标识符,通常程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引(Primary Key)一般是顺序的,不需要磁盘的随机读取。但是不可能每张表上只是有一个聚集索引,更多情况下,一张表上有多个非聚集的辅助索引(secondary index)。当非聚集索引不是唯一索引,对非聚集索引叶子节点的插入不再是顺序的了,这时候就需要离散的访问非聚集索引页,而由于随机读取地存在会导致插入性能下降。
      而Insert Buffer对于非聚焦索引的插入和更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若是在,则直接插入;若不在,则先放入到一个Insert Buffer对象中,好似欺骗数据库这个非聚焦索引已插入到页子节点,而实际并没有,只存放在另一个位置。然后再以一定频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中,这就大大提高了对非聚集索引插入的性能。
      使用Insert Buffer须同时满足以下两个条件:

      • 索引是辅助索引(secondary index)
      • 索引不是唯一(unique)的
    • Change Buffer
      Change Buffer可以看作是Insert Buffer的升级。innoDB存储引擎可以对DML操作——insert、delete、update都进行缓冲,分别是:insert buffer、delete buffer、purge buffer。

      Change Buffe适用的对象依然是非唯一的辅助索引

      对一条记录的update操作可分为两个过程:

      • 将记录标记为已删除
      • 真正将记录删除

      delete buffer对应第一个过程,标记为删除。purge buffer对应第二个过程,将记录真正删除。

    • Insert Buffer的内部实现

      Insert Buffer的数据结构是一颗B+树。这颗B+树存放在共享表空间中,默认是ibdatal中。

      insert buffer由叶节点和非叶节点组成,非叶节点存放的是查询的search key(键值),构造如图所示:

      ![](assets/img/mysql/insert buffer.jpg)

      search key一共占用9个字节,space表示待插入记录所在表的表空间id,每个表有一个唯一的space id。space占用4个字节。marker占用1字节,它是用来兼容老版本的insert buffer。offset表示页所在的偏移量,占用4字节。

    • Merge Insert Buffer
      Merge Insert Buffer操作可能发生以下几种情况:

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

    • Insert Buffer带来的是性能上的提升,那doublewrite(两次写)带来的是数据页的可靠性
      Innodb在写某个页到表中,而这个页只写了一部分就发生了宕机。如果是写失效,可以通过重做日志恢复。如果页本身已经发生了损坏,再重做已经没有了意义。也就是说,在应用重做日志前,用户需要一个副本,当写失效时,可以通过页的副本来还原该页,再进行重做,这就是doublewrite。
      doublewrite体系结构图如下所示:

      doublewrite由两部分组成,一部分是内存中的doublewrite buffer,大小为2MB,另一部分是物理磁盘上共享表空间中连续的128个页,即两个区(extent),大小也为2MB。在对缓冲池的脏页进行刷新时,并不直接写入磁盘,而是通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后再通过doublewrite buffer再分两次,每次1MB顺序写入共享表空间的物理磁盘,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。
  3. 自适应哈希索引
    innodb会监控对表上各索引页的查询。如果观察到哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引(A daptive Hash Index, AHI)。AHI是通过缓冲池的B+树页构造而来,因此建立的速度很快,而且不需要对整张表结构建立哈希索引。innodb回根据访问频率和模式自动为某些热点建立哈希索引。
    AHI有一个要求,即对这个页的连续访问模式必须是一样的,访问模式是以下情况:

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

    若交替进行以上查询,那么innodb不会建立AHI,AHI还有以下要求:

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

    哈希所以只能用来搜索等值查询

  4. 异步IO
    为了提升磁盘操作性能,当前的数据库系统都采用异步IO(Asynchronous IO, AIO)的方式来处里磁盘操作,innodb也是如此
    与AIO对应的是Sync IO,即每进行一次IO操作,需要等待此次操作结束才能继续接下来的操作,如果发出的是一条索引扫描的查询,那这条SQL语句可能需要扫描多个索引页,也就是需要进行多次的IO操作,每扫描一页并等待完成后再进行下一个操作,这是没有必要的。用户可以在发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完成,等待所有IO操作的完成,这就是AIO。

  5. 刷新邻接页
    Flush Neighbor Page工作原理为:当刷新一个脏页时,innodb会检测该页所在区(extent)的所有页,如果是脏页,那么一起刷新。这样做的好处显而易见,但需要考虑以下问题:

    • 是不是可能将不怎么脏的页进行了写入,而该页之后又会很快变成脏页?
    • 固态硬盘有着较高的IOPS,是否还需要这个特性?
上一篇:
mysql技术内幕-文件
下一篇:
mysql技术内幕-数据库和实例