MySQL技术内幕InnoDB存储引擎(二)——InnoDB存储引擎
阅读原文时间:2021年10月16日阅读:1

是一个高性能、高可用、高扩展的存储引擎。

InnoDB存储引擎主要由内存池和后台线程构成。

其中,内存池由许多个内存块组成,作用如下:

  • 维护所有进程和线程需要访问的内部数据结构。
  • 缓存磁盘上的数据,提高处理器读取速度,当数据被修改的时候也是先修改这里的数据,再被后台线程写到内存上去。
  • 重做日志(redo log)缓冲。

后台线程的主要作用:

  • 负责刷新内存池中的数据,以保证缓冲池中数据是最近的数据。
  • 将缓冲中的数据刷新到磁盘上。
  • 保证数据库发生异常的时候 ,数据库恢复到原先正常的状态。

后台线程

后台线程由很多类型,主要包括:

  1. Master Thread
    最核心的后台线程,主要负责将缓冲池的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲、undo页的回收。
  2. IO Thread
    InnoDB中大量使用了AIO来处理些的IO请求,提高数据库性能,这个线程的作用就是负责这些IO的回调。
  3. Purge Thread
    用来已经使用并分配的undo 页。
  4. Page Cleaner Thread
    减轻master线程的工作,将脏页的刷新操作放到了这个线程里单独完成。

内存

  1. 缓冲池
    用于弥补CPU和磁盘速度差距太大的问题,CPU直接操作缓冲,然后由其他线程将在缓冲和磁盘之间做衔接。
    缓冲池中的数据类型有索引页、数据页、undo页、插入缓冲、自适应哈希索引、锁信息和数据字典信息等。

  2. LRUList 、Free List 和 Flush List

    • LRU列表是用来管理已经被读取的数据页。
    • Free列表是用来存储没有被使用的空闲页。
    • Flush列表用来存储被修改过的脏页,脏页既存在于LRU列表,也存在于Flush列表。LRU列表管理缓冲池中页的可用性,二Flush列表管理将页刷新回磁盘,二者不影响。
  3. 重做日志缓冲
    线程先将重做日志放到这个缓冲区,然后将按一定频率将其刷新到重做日志文件。

  4. 额外的内存池
    数据结构本身的内存进行分配的时候,先从额外的内存池中进行申请,这里不够,才从缓冲池里申请。

InnoDB缓冲池的LRU机制

朴素的LRU就是利用一个先进先出的队列,将最近用的使用的页放在队列的前端,最久远使用的页就会被排挤到后面,当缓冲池满了的时候,就将后面的页出队。

LRU的作用

提高缓冲区的利用率和降低CPU读取数据的总时间。每次CPU读取数据,会先去缓冲池里找,如果命中了,就直接提交;如果没有,就会去磁盘里提取,然后存储在缓冲区。因为缓冲池的大小是有限的,只能让部分数据存在里面,LRU算法就是确定哪些数据该留在缓冲区,哪些数据该被丢掉。

InnoDB的LRU优化

InnoDB对传统的LRU算法做了一些优化。在LRU队列中加入了midpoint位置,对于新加入的页,不会被直接放大列表头部,而是插入到midpoint的位置,一般默认位置是5/8长度处。midpoint前面的队列称为热点数据,而后面的就是old数据。
InnoDB通过一个时间参数,规定将读取到页等待多久的时间就会被加入到LRU队列的热点部分。

为什么需要加入midpoint?

如果采用朴素的LRU算法,将新添加的页直接放到头部,这样,可能会将热点的数据挤出去,从而降低了效率。通过加入midpoint,可以将热点的数据一直维持在midpoint之前的位置,将不常用的数据放在之后,这样,不常用的数据更有可能被先丢出去啊。
而新添加的数据还不知道是不是一个热点的数据,所以先放在midpoint位置上,然后通过midpoint和时间参数的配合,就能判断它是不是热点的。这样热点的数据就尽量不会被刷出。

undo log 和 redo log

在上面出现了undo log 和redo log 两个内容,在这里解释区分一下。
数据库通常借助日志来实现事务,常见的有undo log、redo log,undo/redo log都能保证事务特性,undolog实现事务原子性,redolog实现事务的持久性。

  • undo log
    记录中存储的是老版本数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着undo链找到满足其可见性的记录。

  • redo log
    就是保存执行的SQL语句到一个指定的Log文件,当mysql执行数据恢复时,重新执行redo log记录的SQL操作即可。

作用:

  • 数据持久化
    redo log占满的时候,就会将相应的脏页写到内存,然后释放log。
  • 数据恢复
    如果出现宕机,就能重新执行redo log中的语句,恢复缓存中的数据。

数据库线程需要将脏页从缓存池中刷新到磁盘,如何控制刷新的时间和频率对数据库的性能和防止数据丢失就很重要。

作用

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

具体的就是每次刷新多少脏页到磁盘,每次读取哪些脏页,什么时候出发刷新。

具体实现

InnoDB将CheckPoint分为两种:

  • Sharp CheckPoint
    用于在数据库关闭的时候将所有脏页都刷新回磁盘。也可以设置运行的时候使用 Sharp CheckPoint,但是这样回降低数据库的可用性。
  • Fuzzy CheckPoint
    只刷新部分脏页。

Fuzzy CheckPoint的发生条件

  1. Master Thread CheckPoint
    这个检查点由Master线程发起,频率一般是每秒或者每十秒。是异步的,不会阻塞用户查询线程。
  2. Flush_LRU_List CheckPoint
    需要保证LRUList中一直有100个空闲页,如果没有,就要将尾部的页移除,如果这些页中有脏页,这个检查点起作用了。
  3. Async/Sync Flush CheckPoint
    指重做日志文件不够的情况下,需要将一些页刷新回磁盘,这样就能释放一部分重做日志的空间。此时的脏页是从脏页列表中获取的。
  4. Dirty Page too much Checkpoint
    脏页数量太多,就强制执行检查点。

由多个循环组成,包括主循环、后台循环、刷新循环和暂停循环。

主循环

主循环包括两个部分:每秒一次的操作和每十秒一次的操作。

每秒一次的操作包括:

  • 日志缓冲刷新到磁盘(总是)。
  • 合并插入缓冲(可能)。
  • 最多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)。
  • 日过没有用户活动,则切换到background loop(可能)。

所谓可能,就是不会每次都发生的操作。

每十秒一次的操作包括:

  • 刷新100个脏页到磁盘(可能)。
  • 至多合并5个插入缓冲(总是)。
  • 将日志缓冲刷新到磁盘(总是)。
  • 删除无用的undo页(总是)。
  • 刷新100个或者是10个脏页的磁盘(总是)。会根据脏页比例控制刷新数量。

后台循环

如果没有用户活动或者数据被关闭了,就会切换到这个循环。

执行的操作包括:

  • 删除无用的undo页(总是);
  • 合并20个插入缓冲(总是;
  • 跳回到主循环(总是);
  • 不断刷新100个页直到符合条件(可能,跳转到刷新循环中完成)。

线程挂起

如果刷新循环都没有事情可以做,就会被切换到暂停循环,将master thread挂起。

版本改进

之前的刷新数量都是硬编码,是写死的。就会出现刷新数量低,线程忙不过来的问题。

后来改进这个问题,将刷新的数量与合并插入缓冲的数量设置为百分比,根据系统性能做出更改。还有就是自适应刷新,根据脏页的比例,控制刷新频率。

对于脏页的刷新操作,被从master线程中分离到了单独的page cleaner线程中了。

插入缓冲

Insert Buffer

解决问题:对于使用非聚集索引,插入操作不再是顺序的,需要离散地访问非聚集索引页,由于随机读取导致插入操作的性能下降。

目的:提高非聚集索引插入的性能。

实现原理:对于非聚集索引的插入和更新操作,先判断插入的非聚集索引页是不是在缓冲池中,如果在,就直接插入;如果不在,就先放到一个插入缓冲对象,标记为已插入,然后就等待到以后以一定频率和情况进行真正的插入缓冲和辅助索引页子节点的合并操作。这样可以将多个插入合并到一个操作中,大大提高了非聚集索引的性能。

使用插入缓冲需要两个条件:

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

Change Buffer

插入缓冲的升级版。

包括Insert Buffer、Delete Buffer、Purge Buffer,分别对应INSERT DELETE UPDATE操作的缓冲。

对记录进行UPDATE操作可能分为两个过程:标记已删除,真正删除记录。

Insert Buffer 内部实现

数据结构就是一棵B+树。

当辅助索引需要插入到页的时候,如果这个页不在缓冲池中,那么引擎就先构造一个search key,接下来查村Insert Buffer这棵B+树,然后将这个记录插入到树的叶子节点中去。

叶子节点的信息包括记录的表空间ID,页所在的偏移量,记录的插入顺序等。

缓冲中的记录如何合并到真正的辅助索引中呢?

三种情况回进行合并操作:

  • 辅助索引页被读取到了缓冲池当中;
  • 辅助索引页已经无可用空间;
  • 主循环中的合并。

两次写

目的:提高数据的页的可靠性。

工作原理:对缓冲池中的脏页进行刷新的时候不会直接写到磁盘,而是先将脏页复制到内存的doublewrite buffer中,然后通过这个buffer分两次写到共享表空间的物理磁盘上,然后再同步磁盘。

自适应哈希索引

目的:一般通过B+树来查询定位页,查询次数取决于树的高度,一般为三到四层,也就是要查三到四次,而通过哈希索引就只需要查一次就能定位到数据。但是不能为所有数据都建立哈希索引,开销上可能划不来。

作用:引擎对表上各项索引进行查询,如果观察到的建立哈希索引可以带来速度提升,就建立哈希索引,这就是自适应哈希索引。

InnoDB存储引擎会根据访问的频率和模式来自动为某些热点页建立哈希索引。

异步IO

提高磁盘的操作性能,当前的数据库都是采用异步IO来操作磁盘。

刷新邻接页

工作原理:当刷新到一个脏页的时候,存储引擎就会顺便检测该页所在区的所有页,如果存在脏页,就一起刷新了,这样可以将多个IO合并为一个IO进行操作,大大提高对机械磁盘的操作性能。

通过这一节内容,很好地理解和总结了InnoDB的相关知识,尤其是LRU、插入缓冲部分,如果学习过操作系统,那么将会对内存管理相关等知识得到一个更全面和深刻的理解。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器