2. InnoDB的基础架构与关键技术

专栏地址:

MySQL系列文章专栏



1. 整体架构

InnoDB存储引擎的结构主要分为两部分:内存结构和磁盘结构,前者位于MySQL的进程内,后者位于文件系统中。此外,还有一组后台线程负责刷新内存中的数据。

在这里插入图片描述

内存结构主要包含:

  1. 缓存池 Buffer Pool
  2. 写缓存 Change Buffer
  3. 自适应哈希索引 Adaptive Hash Index
  4. 日志缓存 Log Buffer

1.1 内存结构

1.1.1 缓存池 buffer pool

InnoDB利用内存来弥补硬盘速度对数据库整体性能的影响。缓存池中的数据页类型有:索引页、数据页、undo页、change buffer写缓存等信息。InnoDB在读取和修改页的时候,都会首先尝试修改缓存中的页,同时利用重做日志redo log和checkpoint机制,异步地将脏页刷新落盘。

在MySQL服务器上,一般将高达70%-80%的内存用于InnoDB缓存。
在这里插入图片描述

缓存池的LRU算法

缓存池中的页主要来源于:查询读取的页面和后台线程预读的页面,使用优化的LRU(Least Recently Used,最近最少使用)算法对缓存页进行管理。

传统的LRU算法,最新加入的数据被放置在链表的头部,同时将末尾超出容量的数据淘汰掉。当链表中的数据被命中时,将其移动到首部。这样,保证了最近被访问和访问最频繁的数据位于链表的首部,使用最少的数据会先被淘汰掉。但其缺点主要在于无法解决预读失效缓存池污染问题:

  1. 预读失效
    预读的页并没有被访问过,需要尽快地从LRU列表中被驱逐。
  2. 缓存池污染
    某些SQL会扫描大量的行,可能会将缓存池中大部分甚至所有的页替换出去。而这些页可能仅仅是这次查询需要的,而被替换出去的是热数据。会造成缓存池性能的急剧下降。

InnoDB缓存池引入了Midpoint,将LRU列表分为 new sublist(young区)和 old sublist(old 区),默认分别占据5/8和3/8的空间。新加入的页被放置在Midpoint处,当其被访问且在old sublist列表中的停留时间超过阈值(默认1秒)时,晋升到new sublist的头部,称之为page made young。同时,new sublist尾部的数据页会被挤到old sublist,称之为page become old。最终,old sublist尾部超出容量的数据页会被最终淘汰。这样,预读失效的页和会造成缓存池污染的页,只会在old sublist中停留,new sublist中真正的热数据并不会被替换出去。

在这里插入图片描述

预读
InnoDB会异步将可能被访问的页面预先读取到缓存池中。预读分为:线性预读和随机预读。线性预读将下一个区的页读取到缓存池中,随机预读将当前区剩余的页读取到缓存池中。

脏页刷新

InnoDB会在以下情况将缓存池中的脏页刷新到磁盘:

  1. 系统空闲时
  2. 缓存池空间不足时
  3. redo log 空间不足时
    脏页刷新由Page Cleaner Thread线程负责。

1.1.2 change buffer 写缓存

InnoDB利用缓存池buffer pool来加速数据页的访问速度,对数据页的修改也会先在缓存池中进行更新,并在随后适当的时候对脏页进行刷新。当待修改的页不在缓存,若将页先从磁盘中读取到缓存池中再进行修改,会产生较高的随机IO开销。为此,InnoDB引入了change buffer,将对辅助索引(二级索引)的操作缓存下来,以此减少随机读IO,并达到操作合并的效果。

例如,当需要更新一个辅助索引的叶子节点时,如果

  1. 该数据页在缓存池中,则直接更新缓存中的页,并在随后进行脏页刷新。
  2. 该数据页不在缓存池中,InnoDB会将这些操作缓存在change buffer中,在下次读入该数据页时,在进行merge操作。

Change Buffer支持insert、update、delete操作,不仅存在于缓存池中,还存储在系统表空间中(以普通B+树的方式),缓存的对象是:

  1. 普通的辅助索引的叶子节点,聚集索引不可(PS:聚集索引的插入一般为顺序IO);
  2. 唯一索引的删除操作,由于需要查询数据页以判断唯一性,所以不能缓存插入操作。

merge

将Change Buffer中的操作应用到相应的数据页的操作称之为merge,触发的时机有:

  1. 查询访问了相应的数据页
  2. Master Thread会定期执行merge操作

merge的执行流程:

  1. 从磁盘读取原始的数据页到缓存池中
  2. 从change buffer中找到该数据页的change buffer记录,可能有多条,依次应用
  3. 写redo log,包括数据页的变更和change buffer的变更
  4. 后续的脏页刷新

适应场景

对同一个数据页积累的操作越多,change buffer进行操作合并的收益越大。其主要适用于:写多读少场景,譬如账单、日志类应用,数据页在写完后被立即访问的概率比较小。相反的,如果一个业务的更新模式是写入后立刻进行查询,那么随机IO次数不仅不会减少,反而增加了change buffer的维护代价。

Change Buffer的崩溃恢复

在事务提交的时候,Change Buffer中的操作会记录到redo log中,在发生意外故障后,可以利用redo log进行崩溃恢复。

Change Buffer 结构图
在这里插入图片描述

Change Buffer思维导图
在这里插入图片描述

1.1.3 自适应哈希索引 AHI

一般情况下,B+树的高度为2-4层,故需要2-4次IO才可以定位到目标数据页。譬如,使用默认自增主键,每条记录在1KB的情况下,一颗3层高的聚集索引B+树大概可以存储2000万条左右的数据。而哈希索引的时间复杂度在O(1),一般仅需一次IO即可定位到数据。

为了减少重复的寻路开销,InnoDB会自动根据访问模式(查询条件)和频率来对热点页建立哈希索引,以便快速定位到叶子节点。其key由查询条件中索引的前N列构成,value是叶子节点的位置。AHI是内存结构,可以看作是索引的索引。

建立AHI的前提是:

  1. 对一个页的连续访问模式相同
  2. 页以该模式访问的次数达到以一定的阈值

AHI的适应场景:

  1. 等值查询
  2. 查询条件中前缀索引的列数大于AHI中的列数

在高并发下,AHI可能会成为竞争资源。若查询模式无法从AHI中获益,可考虑关闭AHI以减少不必要的性能开销。

1.2.4 log buffer 日志缓存

log buffer主要对重做日志redo log进行缓存,一般不需要设置太大,Master Thread 每秒会对redo log进行落盘,在事务提交或者log buffer空间过小时也会进行刷新。

详见日志系统章节

1.2 硬盘结构

存储在硬盘上的结构主要有:

  1. 表空间
    • 系统表空间(共享表空间):数据字典、double write buffer、change buffer、undo log
    • 独占表空间:存储每张表的数据和索引
    • undo表空间:undo表空间包含undo log撤销记录的集合
    • 临时表空间:用户创建的临时表
  2. redo log

当数据库意外宕机时,InnoDB存储引擎会利用重做日志进行故障恢复.InnoDB存储引擎至少有1个重做日志文件组,每个组下至少有2个重做日志.日志组中的每个重做日志文件大小一致,并以循环追加写的方式运行.

详见事务章节

1.3 后台线程

InnoDB存储引擎是一个多线程架构,其后台线程主要有:Master Thread、IO Thread、Page Cleaner Thread和Purge Thread。

1.3.1 Master Thread

InnoDB的主要工作都是在Master Thread中完成的,其线程优先级最高。Master Thread内部有多个循环组成:主循环、后台循环、刷新循环、暂停循环。其主要工作有:

  1. redo log的刷盘
  2. merge change buffer (合并写缓存)

脏页刷新和undo 页的回收现由Page Cleaner Thread和Purge Thread负责。

1.3.2 IO Thread

InnoDB使用了大量的AIO,IO Thread的主要工作是处理这个IO请求的回调,主要有:

  1. write thread
  2. read thread
  3. insert buffer thread
  4. log thread

每个类型的IO Thread都有多个。

1.3.3 Page Cleaner Thread

Page Cleaner Thread主要负责将缓存池buffer pool中的脏页刷新到磁盘中。

1.3.4 Purge Thread

负责收回undo log页以及数据页空间。

由于InnoDB支持MVCC,所以对记录的删除操作、undo log的回收操作等不能立刻进行,而是需要在这些旧版本没有被事务引用时再进行清理操作。

update undo log会按照顺序放到history list中,后台Purge Thread会在定期扫描,清理无用的undo log。另外,还会对标记为删除行记录(delete flag)进行彻底的删除,即该记录占用的空间放入PAGE_FREE链表中,以便复用。

insert undo log,由于只对当前事务生效,所以在事务提交后可以直接删除,不需要purge操作。

2. InnoDB的关键技术

InnoDB的关键技术主要有以下5个:

  1. 写缓存 Change Buffer
  2. 两次写 Double Write
  3. 自适应哈希索引 Adaptive Hash Index
  4. 异步IO Async IO
  5. 刷新邻接页 Flush Neighbor Page

2.1 Double Write

Double Write主要是为了提升数据页可靠性,防止部分写失效导致的数据丢失。因为InnoDB数据页一般为16K,而文件系统的页大小为4K,所以操作系统可能无法保证InnoDB数据页的原子写入。倘若在刷新页的过程中,服务器宕机了,则会导致原始数据页的损毁。重放redo log也无法解决部分写失效问题,因为重做日志记录的是对数据页的修改操作,如果这个数据页本身就是损坏的,对其应用redo log中的修改也是没有意义的。

InnoDB的解决方案是:拷贝一份数据页的副本,即Double Write。当脏页要刷新时,Doube Write的主要流程是:

  1. 先将Buffer Pool中的脏页拷贝到Double Write Buffer中。
  2. 将Double Write Buffer以顺序追加写的方式实时刷盘(系统表空间),同步IO。
  3. 再将Double Write Buffer中的页写到相应的表空间,此时为随机IO,异步IO。异步IO回调函数中会进行完整性校验。

Double Write Buffer不仅仅存在于内存中,是一个内存/磁盘的两层结构

在这里插入图片描述

当发生了部分写失效时,可以通过系统表空间的Double Write Buffer进行恢复,随后再应用redo log重做日志,完成完整的故障恢复流程。

如果文件系统能够提供页大小的原子写入,提供防范部分写失效的解决方案,那么可以关闭Double Write。

2.2 异步IO

InnoDB利用AIO来提高磁盘操作性能,同时还会进行IO Merge操作,将多个IO操作合并为1个IO操作。

2.3 刷新邻接页 Flush Neighbor Page

InnoDB在进行脏页刷新时,会检测该页所在区下面的所有页,如果也是脏页,那么一并刷新。这样可以减少IO次数。但是可能存在:

  1. 将不怎么脏的页刷新了,该页立马又变脏了
  2. SSD是否可以考虑关闭

参考

InnoDB 存储引擎—MySQL官网
《MySQL技术内幕(InnoDB存储引擎)》
《MySQL实战45讲》 极客时间
《InnoDB架构,一幅图秒懂!》 微信公众号-架构师之路
InnoDB缓存池

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值