InnoDB引擎 内存概览

本文最后更新于:2022年7月24日 下午

概览:InnoDB引擎

InnoDB特点

  1. 行锁设计
  2. 支持MVCC
  3. 支持外键
  4. 提供一致性非锁定读

InnoDB 内存

InnoDB内存分类:

  1. 缓冲池
  2. 重做日志缓冲
  3. 额外内存池

InnoDB存储引擎基于磁盘存储,由于CPU速度与磁盘速度之间的鸿沟,需要使用缓冲池技术来提高数据库的整体性能。

缓冲池其实就是一块内存区域,通过内存速度来弥补磁盘速度较慢对数据库的影响。

整体操作:

  • 数据库读取页,将磁盘中的页放到缓冲池,下一次再读相同页时,首先判断该页是否在缓存中,若在,命中缓存,否则读取磁盘。
  • 数据库修改页,先修改在缓存中的页,然后再以一定的频率写回磁盘。 —— 使用Checkpoint机制刷回。

缓存池中的数据:

  • 索引页
  • 数据页
  • undo页
  • 插入缓冲(insert buffer)
  • 自适应哈希索引
  • 锁信息等

InnoDB还允许多个缓冲池实例,每个页根据哈希值分配到不同的缓冲池实例中 —— 减少数据库内部资源竞争,增加数据库并发处理能力。

重做日志缓冲 redo log buffer

先将重做日志信息放入缓冲区,在按照一定的频率刷到重做日志文件中。

一般情况下,重做日志缓冲区不大,8MB,因为每一秒都会将重做日志缓冲刷新到文件。

刷新场景:

  1. Master Thread每一秒刷新,
  2. 每个事务提交时会刷新,
  3. 当重做日志缓冲池剩余空间小于一半时,也刷新

InnoDB内存 —— LRU、Free List、Flush List

LRU —— Latest Recent Used,最近最少使用。

缓冲池使用LRU来管理,最频繁使用的再LRU List的前端,最少使用的在LRU List的尾端,当缓存池放不下时,首先释放尾端数据。

LRU优化

InnoDB在LRU列表中加入了midpoint位置,新读取到的页,不是放在LRU列表的首部,而是放在LRU列表的midpoint位置。

1
参数 innodb_old_blocks_pct

默认位于距离首部长度5/8的位置。

InnoDB存储引擎,把midpoint之后的列表称为old列表,之前的列表称为new列表 —— 存储活跃的热点数据。

为什么要做这样的优化呢?

对于某些sql,它会做索引或者数据的扫描操作,会加载很多的页,但是这些页可能并非会被频繁使用,如果页放到了LRU首部,会把很多真正的热点数据移除,影响性能。

那么就还需要一个机制,来讲那个数据放入热点数据中

1
innodb_old_blocks_time

这个用来表示当前页读取到midpoint位置之后需要过多久才会加入到LRU列表前端。

Free List

表示当前还空闲着的缓冲池的页的集合。

当Free List不为空时,使用其中的空闲页;当没有空闲页的时候,新增页面需要使用LRU来淘汰替换。

但是并不是所有申请的页都会放到LRU列表中,类似自适应哈希索引、Lock信息等时不需要使用LRU维护的。

参考链接:https://blog.csdn.net/lijuncheng963375877/article/details/124011204

Flush List

LRU列表中被修改的页称为脏页,即缓冲池中的数据和磁盘中的数据不一致。

Flush List列表中的页就是脏页,使用Checkpoint机制来将页刷回磁盘。

这里的脏页既在LRU中,又在Flush List中。

Checkpoint技术

回顾:

  1. 引入缓冲池技术来协调CPU与磁盘之间速度不一致的问题。数据更新操作都是先在缓冲池中完成的,数据修改后就是脏页,需要刷回磁盘。
  2. 一修改就刷新磁盘这样的开销也是很大的,例如修改总是在某几页中。此外如果刷盘的过程中磁盘宕机,数据就丢失无法恢复,由此引入了Write Ahead Log策略,先写重做日志,再刷盘

对于重做日志和刷盘来说,要考虑几个点:

  1. 对于重做日志,我怎么知道数据库的哪一部分数据还没有更新呢?
  2. 缓冲池不够用时?脏页如何处理?
  3. 如果重做日志不可用了?怎么处理?

引入checkpoint,它表示在其之前的页一定都刷回了磁盘,宕机发生时,只对checkpoint及其之后的页做恢复,这样可缩短恢复时间

当缓冲池不够用的时候,LRU会溢出最近最少使用的页面,若页面为脏页,则需要强制执行checkpoint,将脏页刷回磁盘。

脏页的修改数据都是存放在redo log中的,如果脏页不再管理了,那么对应的redo log中的数据应该刷到磁盘,下一次读取的时候就是最新的数据了。

那么什么时候会出现重做日志不可用呢?

设计上,重做日志是循环使用的,而不是无限增大的,如果出现了重做日志写了一圈写到了checkpoint的位置,并且还需要继续写的情况时,这是重做日志就不可用了。

做法:先强制checkpoint技术,刷新页,再继续使用重做日志。

参考链接:https://www.jianshu.com/p/69da2c9939d2

Async/sync Flush Checkpoint —— 解决重做日志不可用

1
2
# 计算未刷盘的数据量
checkpoint_age = 已经写入的重做日志位置LSN - 已经刷盘的chechpoint_lsn

两个标志位

1
2
async_water_mark = 0.75 * total_redo_log_file_size
sync_water_mark = 0.9 * total_redo_log_file_size
  • checkpoint_age < async_water_mark,不需要刷盘
  • async_water_mark <= checkpoint_age < sync_water_mark, 刷盘,使其满足第一个条件位置
  • sync_water_mark > checkpoint_age ,一般很少发生,触发Sync Flush操作,使其满足第一个条件。

使用这种方式,保证了重做日志循环使用的可能性。

InnoDB其他优点 —— 插入缓冲

Insert Buffer是一种提高非主键索引、非唯一索引的数据的索引插入、更新性能的设计。

InnoDB存储引擎的索引使用了B+树,索引可分为聚簇索引和非聚簇索引两类。

新插入行时,一般是按照主键递增的顺序做的插入,对于聚簇索引来说,一般是顺序的,不涉及随机读

但是对于表的其他字段,可能会产生一些非聚簇的、非唯一的索引,对于这种索引的叶子节点插入不是顺序的了,而是需要离散的访问非聚集索引页,这是随机读的过程。

Insert Buffer设计:对于非聚簇索引的插入或者更新操作,不是每一次直接插入到索引页中,而是:

  1. 先判断非聚集索引页在缓冲池吗?在的话直接更新
  2. 不再的话,先放入Insert Buffer之中
  3. 再以一定的频率和情况将Insert Buffer中的数据和二级索引叶子节点合并。

适用条件:非聚簇索引、非唯一索引。

为什么是非唯一索引?

因为插入缓冲中时,引擎并不去查找索引页来判断插入的记录的唯一性,如果要验证唯一性,就需要离散读,那就没必要使用这个了。

缺点:当大量数据都使用了Insert Buffer时候,若发生了宕机,则恢复的时候可能需要很长时间。

InnoDB 1.0.x之后,引入了Change Buffer,可看作Insert Buffer的升级版。

InnoDB其他优点 —— 两次写 double write

数据库宕机的时候,可能引擎正在写入某个页到磁盘中,但是只写了一部分,这种称为部分写失效

发生写失效,可以使用重做日志来恢复吗?

  • 重做日志记录的是对一个页的物理操作,eg,偏移量800的地方,写’aaaa’
  • 如果页发生了损坏,那么重做日志就没有意义了。
  • 即,重做前,需要一个页的副本,写失效时,先通过页副本还原,再写重做日志。

实现:

doublewrite由两部分组成:

  1. 内存中的doublewrite buffer, 2MB
  2. 物理磁盘上共享表空间中的连续128页(一页=16KB),也就是2个区,大小也2MB

对缓冲池脏页刷新时,并不直接写磁盘,而是先将脏页复制到内存中的doublewrite buffer,然后将doublewrite buffer分成2份,每次1MB顺序写入共享表的物理磁盘,最后马上同步磁盘。

未完待续…

参考书籍:《MySQL技术内幕 —— InnoDB存储引擎》