InnoDB体系架构

InnoDB体系架构

下图来自MySQL技术内幕
innodb内存结构.png

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

后台线程

Master Thread

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

IO Thread

InnoDB存储引擎大量使用了AIO(Async IO)来处理写IO请求,这样极大地提高了数据库的性能。IO Thread的工作主要是负责这些IO请求的回调处理。

Purge Thread

事务被提交后,其所使用的undolog可能不再需要,因此需要Purge Thread来回收已经使用并分配的undo页。

Page Cleaner Thread

Page Cleaner Thread是在InnoDB1.2.x版本中引入的。其作用是将之前版本中脏页的刷新操作都放入到单独的线程中来完成。目的是为了减轻原Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

内存

对于使用 InnoDB 作为存储引擎的表来说,不管是用于存储用户数据的索引,还是各种系统数据,都是以页的形式存放在表空间中的,而所谓的表空间只是 InnoDB 对文件系统上一个或几个实际文件的抽象,也就实际数据说到底还是存储在磁盘上的。

磁盘的速度很慢,怎么能配得上“快如闪电”的CPU 呢?

InnoDB 存储引擎在处理客户端的请求时,当需要访问某个页的数据时,就会把完整的页的数据全部加载到内存中。

也就是说即使我们只需要访问一个页的一条记录,那也需要先把整个页的数据加载到内存中。

缓冲池

缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。

对于页修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘并不是在每次页发生更改时触发,而是通过一种叫Checkpoint的机制刷新回磁盘。这样最也是为了提高数据库的整体性能。

缓冲池中存储了索引页和数据页,占缓冲池很大一部分,其他比如undo页、插入缓冲、自适应哈希索引、InnoDB存储的锁信息、数据字典信息等都会在缓冲池中缓存,以下是InnoDB存储引擎中内存的结构情况:
innodb内存数据.webp

数据组织方式

缓冲池中默认的缓存页大小和在磁盘上默认的页大小是一样的,一般是16KB。

为了更好的管理这些在缓冲池中的缓存页,InnoDB为每一个缓存页都创建了一些所谓的控制信息。

这些控制信息包括该页所属的表空间编号、页号、缓存页在缓冲池中的地址、链表节点信息、一些锁信息。

FreeList

数据库启动时InnoDB向操作系统申请一片内存区域,之后InnoDB会将这个内存区域按照数据页的大小进行划分,之后将他们的控制体连成一个链表,这个链表就叫做Free List。当InnoDB需要内存缓冲一些数据的时候,就会到Free List中取一定数量的控制块到LRU List,当数据同步磁盘、释放内存的时候又会将被释放数据页的控制块从LRU List加入到Free List中。
InnoDB缓冲池数据组织
简单地说,FreeList指的就是缓冲池的空闲空间,LRU List中则是已缓存的数据。

LRUList

已缓存的热点数据在LRUList中,使用LRU算法进行管理。
访问数据页时,它会将控制块的表空间和页号进行哈希,当InnoDB需要某个数据页时就可以直接定位,而不需要从头遍历。
但是Innodb采用的不是传统的LRU算法,即访问移到头部,数据满淘汰尾部。在InnoDB中,传统的LRU会遇到两个问题:

  • 预读失效
    由于预读 (Read-Ahead),提前把页放入了缓冲池,但最终 MySQL 并没有从页中读取数据,以前的的页被刷出,但新页并未被读取,称为预读失效。
  • 缓冲池污染
    当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染。
  1. 让预读失败的页,停留在缓冲池 LRU 里的时间尽可能短;
  2. 让真正被读取的页,才挪到缓冲池 LRU 的头部;以保证,真正被读取的热数据留在缓冲池里的时间尽可能长。

所以InnoDb将Lru List进行划分,划分成Young、Old区域,热数据都存在于Young区域,新读入的数据会加到Old区域链表的头部,并且淘汰尾部的数据。同时加入了一个“old 区停留时间”的机制: 在 old 区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续再次访问的时间与第一次访问的时间在某个时间间隔内(即该缓存页在 old 区的存在时间在某个时间间隔内),那么该页面就不会被从old 区移动到 Young 区的头部。

Flush List

在LRU中的页被修改后是脏页,InnoDB会在特定的时间通过CheckPoint机制将它们刷新到磁盘上。InnoDB通过Flush List来管理这些脏页,当数据页发生修改时它的控制体就会被加入到Flush List中,但是并不会从Lru List中移除。脏页既存在于LRUList中,也存在于FlushList。

重做日志缓冲

从上图看到,InnoDB内存区域除了缓冲池外,还有重做日志缓冲。InnoDB存储引擎首先将重做日志信息放入缓冲区,然后按一定频率将其刷新到重做日志文件。
重做日志缓冲刷新情况如下:

  • Master Thread每1秒将重做日志缓冲刷新到重做日志文件
  • 每个事务提交时会将重做日志缓冲刷新到重做日志文件
  • 当重做日志缓冲池剩余空间小于1/2时,刷新到重做日志文件

额外的内存池

对一些数据结构本身的内存进行分配时,需要从额外的内存池中申请,当该区域内存不够时,从缓冲池申请。例如每个缓冲池的缓冲控制对象信息。

CheckPoint技术

缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此页的操作首先都是在缓冲池中完成的。如果一条改变了页中的记录,那么此时页是脏的,即缓冲池中的页的版本要比磁盘的新。数据库需要将新版本的页从缓冲池刷新到磁盘。
倘若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差。同时,如果在从缓冲池将页的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了Write Ahead Log策略,即当事务提交时,先写重做日志,再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复。这也是事务ACID中D (Durability持久性)的要求。
CheckPoint技术用于解决以下问题:

  • 缩短数据库的恢复时间
    数据库只需对Checkpoint后的重做日志进行恢复因为CheckPoint之前的日志已被刷新到磁盘。
  • 缓冲池不够用时,将脏页刷新到磁盘
    根据LRU算法会溢出最近最少使用的页,若此页为脏页,需强制执行CheckPoint。
  • 重做日志不可用时,刷新脏页
    重做日志是循环使用的,若被覆盖部分的日志还未刷新到磁盘,则需要执行CheckPoint。

Master Thread工作原理

InnoDB存储引擎的主要工作都是在一个单独的后台线程master thread中完成的。通过阅读源码,可知master thread线程优先级最高,其中包括了四个循环:主循环(loop)、后台循环(background loop)、刷新循环(flush thread)、暂停循环(suspend thread)。
loop称为主循环,因为大部分的操作都在该循环中完成的,其中又有两大部分的操作:每秒钟的操作和每十秒钟的操作。

每秒一次的操作

  • 刷新redo log到磁盘,即使这个事务还没提交这就解释了再大的事务提交时间也是很短的。
  • 合并插入缓冲。InnoDB引擎会判断当前一秒的IO次数,如果小于5次,则认为当前IO压力很小,就会执行合并插入缓冲的操作。
  • 刷新脏页到磁盘。InnoDB会判断当前缓冲池中脏页的比例是否超过了配置文件的参数(默认90,代表90%),如果超过这个阈值,则会将至多100个脏页写入磁盘。
  • 如果当前用户没有活动,则切换到background loop。

每十秒的操作

  • InnoDB会判断过去10秒之内的磁盘IO操作是否小于200次,如果是则认为当前有足够的磁盘IO能力,因此将100个脏页刷新到磁盘。
  • 合并至多5个插入缓冲。
  • 将日志缓冲刷新到磁盘。
  • 执行full purge操作,删除无用的undo页。事实上在对MySQL表执行update、delete操作时,原先的行被标记为删除,但是由于一致性读的关系,需要保留这些行版本的信息。在full purge的过程中,InnoDB会判断当前事务系统已被删除的行是否可以删除(比如有时候可能还有查询操作需要读取之前的版本的undo信息),如果可以,InnoDB会立即将其删除。
  • 刷新100个(脏页的比例大于70%)或10个(脏页的比例小于70%)脏页到磁盘。
  • 产生一个检查点(checkpoint)。将最老日志序列号(oldest LSN)的页写入磁盘。

存在的问题

  • InnoDB最多只会刷新100个脏页到磁盘,合并20个插入缓冲,实际上每秒可能会产生大于100个脏页或产生大于20个插入缓冲,当固态硬盘出现时,这个规则很大程度上限制了磁盘IO的性能。并且在发生服务器宕机时由于很多数据还没刷回磁盘,可能恢复需要较长时间。
  • InnoDB默认脏页占缓冲池超过90%的容量时就触发脏页刷新到磁盘的操作,但是这个比例也太大了,很容易造成脏页积压,给数据库服务器带来压力。
  • 回收undo页的数量是固定的。

优化方案

  • innodb_io_capacity,表示磁盘IO的吞吐量,刷新到磁盘页的数量按百分比控制。
  • innodb_purge_batch_size,回收undo页的数量。

InnoDB关键特性

插入缓冲

插入缓冲是为了提高数据库写入的效率,Insert Buffer和数据页一样也是物理页的组成部分,并不属于缓冲池。
数据插入时,数据页是插入到聚集索引中。
InnoDB中,主键是行唯一的标识符,通常插入顺序是按主键递增的,所以插入聚集索引是顺序的,不需要随机访问磁盘。(并不是所有的主键插入都是顺序的,如果主键是uuid这样随机的,那么主键索引并不是顺序的)

但是一张表中除了聚集索引,经常还有多个非聚集索引,非聚集索引数中存储的指针指向聚集索引中的数据页,非聚集索引的插入就无法保证顺序插入,如果离散访问磁盘,效率会很低。

插入缓冲的使用要满足以下两个条件

  • 索引是辅助索引
  • 索引不是唯一,因为在插入缓冲时,数据库并不会去查找索引来判断数据的唯一性,如果查找,肯定又会有离散读取磁盘的情况从而影响插入效率。

Insert Buffer的过程:对于非聚集索引的插入和更新的操作,并不是每次操作都直接插入到索引页中,而是先判断非聚集索引页是不是在缓冲池中,如果在就直接插入,如果不在就先放入到InsertBuffer中,(InsertBuffer是一棵全局的B+树,保存在共享表空间中)然后再以一定的频率和辅助索引页进行合并操作,这样就可以将多个插入操作合并到一个操作中,大大提高了对于非聚集索引插入的性能。

InnoDB1.0.x开始引入了Change Buffer,对DML操作进行缓冲,也是类似的思路,标记删除之后再真正删除。

何时合并索引缓冲?
什么时候将全局B+树上的索引缓冲合并到真正的辅助索引上呢?

  • 辅助索引页被读取到缓冲池
  • 该辅助索引页已无可用空间时
  • Master Thread合并索引缓冲(见上文)

双写

两次写是为了保证InnoDB数据页存储的可靠性,它解决的事部分写失效的问题,也就是当缓存中的脏页刷新到磁盘的时候数据库发生宕机时导致部分数据没有写入到磁盘中(譬如16KB的页,只写了4KB)。
为什么不能使用redo log进行恢复?
重做日志记录的是对页的物理操作,如偏移量800,写’aaaa’记录,如果这个页本身已经发生了损坏,再对它进行重做是没有意义的。所以当部分写失效发生时,我们需要数据的一个副本来恢复。

当数据库缓冲池中的脏页刷新时,并不会直接写入磁盘的数据文件中,而是将脏页先拷贝到内存中的double write buffer,之后再通过double write buffer将数据写入到共享表空间的物理磁盘上(doublewrite页)。写入完成之后再将数据同步到磁盘数据页。这种做法的本质就是再刷新脏页进行脏页数据像磁盘数据写入的时候,先将数据在共享表空间的doublewrite页做一次备份,当写入过程中发生数据库宕机时,可以通过备份数据进行恢复。

自适应哈希索引

数据库的数据结构是B+树,在生产环境中B+树的高度一般为3-4层,故需要3-4次的查询,但是如果使用hash索引的时候只需要一次查询,InnoDB存储引擎会监控对各个索引页的查询,如果观察到建立hash索引可以带来速度提升,则建立hash索引,称之为自适应哈希索引。
自适应哈希索引会占用innodb buffer pool,只适合搜索等值的查询,而对于其他查找类型,如范围查找,是不能使用的。

异步IO

除了异步操作之外,InnoDB的AIO的另一个优势是IO Merge操作,例如访问的几个页如果是连续的,那么可以合并为一次IO操作。

刷新邻接页

当刷新一个脏页时,InnoDB存储引擎会检测该页所在区( extent)的所有页,如果是脏页,那么一起进行刷新。这样做的好处显而易见,通过AIO可以将多个IO写人操作合并为一个IO操作,故该工作机制在传统机械磁盘下有着显著的优势。如果使用的是固态硬盘,且有较高的IOPS,可以考虑关闭该特性。

参考《MySQL技术内幕》
https://zhuanlan.zhihu.com/p/144044156
如果是脏页,那么一起进行刷新。这样做的好处显而易见,通过AIO可以将多个IO写人操作合并为一个IO操作,故该工作机制在传统机械磁盘下有着显著的优势。如果使用的是固态硬盘,且有较高的IOPS,可以考虑关闭该特性。

参考《MySQL技术内幕》
https://zhuanlan.zhihu.com/p/144044156
https://www.corgiboy.com/InnoD%E5%BC%95%E6%93%8E/InnoDB-%E7%BC%93%E5%86%B2%E6%B1%A0/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值