闲聊MySQL:(四)深入分析InnoDB之内存架构

前言

在前两篇中,我们对MySQL的存储引擎InnoDB进行了简要分析,可以点击这里查看:

闲聊MySQL:(三)深入分析InnoDB之多版本控制MVCC

闲聊MySQL:(二)存储引擎之InnoDB浅析

本篇,我们继续对InnoDB的进行分析,来了解一下InnoDB的内存架构组成。

InnoDB架构模型

首先,我们来看一下MySQL 官方文档中给出的InnoDB的架构模型:

InnoDB架构模型

从上图中我们可以看到,InnoDB的架构可以分为两大部分,内存结构和磁盘结构。下面,我们来一起了解一下内存结构的各部分组成。

内存架构

缓冲池(buffer pool)

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页(page)的方式进行管理。但是由于CPU速度和磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池记录来提高数据库的的整体性能。

缓冲池是主存储器中的一个区域,用于在访问时缓存表和索引数据。缓冲池允许直接从内存中处理常用数据,从而加快处理速度。在专用服务器上,通常会将最多80%的物理内存分配给缓冲池。

为了提高大容量读取操作的效率,缓冲池被分成可以容纳多行的页(page)。为了提高缓存管理的效率,缓冲池被实现为链接的页链表(linked list of pages);使用LRU算法的变体,很少使用的数据在缓存中老化(aged out)。

在数据库中进行读取操作,首先将从磁盘中读到的页放在缓冲池中,下次再读相同的页中时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。

具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、更改缓冲区(change buffer)、自适应HASH索引(adaptive hash index)、锁信息(lock info)、数据字典信息等。

InnoDB内存数据对象

不可以简单的认为,缓冲池只是缓存了索引页和数据页,它们只是占缓冲池很大的一部分而已。

下面,我们对这些区域进行依次的介绍。

缓冲池LRU算法

上面我们了解InnoDB的内存结构组成,那么InnoDB是如何对缓冲池中的数据进行管理的呢?

缓冲池使用了LRU(Latest Recent Used,最近最少使用)的变体算法对其页(page)进行管理,当需要空间将新页(page)添加到缓冲池时,最近最少使用的页被淘汰掉,将新的页加入到页列表的中间。此插入策略将页列表视为两个列表:

  • 在头部,是最近访问过的新页的子列表
  • 在尾部,是最近访问的旧页的子列表

此算法保留新子列表中最频繁查询的页。旧子列表包含较少查询的页面,这些页会优先被淘汰。

缓冲池LRU算法

默认情况下,算法操作如下:

  • 3/8的缓冲池专用于旧子列表。
  • 列表的中点是新子列表的尾部与旧子列表的头部相交的边界。
  • 当InnoDB将页读到缓冲池时,它最初将其插入中点(旧子列表的头部)。页的被读取操作,可以由用户指定的操作(如SQL查询)所触发,或者是由InnoDB自动执行的预读操作。
  • 访问旧子列表中的页,可以使其“变年轻”,该操作称为page made young,会将其移动到缓冲池的头部(新子列表的头部)。如果因为需要而读取页,则会立即进行第一次访问,并使页变得“年轻”。如果由于预读而读取了页,则不会重复执行上面的操作。
  • 随着数据库的运行,在缓冲池的较少被访问的页会向列表的尾部移动。新旧子列表中的页随着其他页的变化而变旧。旧子列表中的页也会随着页插入中点而老化。最终,仍然未使用的页到达旧子列表的尾部并被淘汰。

默认情况下,查询读取的页会立即移动到新的子列表中,这意味着它们会更长时间地保留在缓冲池中。

表扫描(例如为mysqldump操作执行,或者SELECT带有no WHERE子句的语句)可以将大量数据带入缓冲池并逐出相同数量的旧数据,即使新数据从未再次使用过。

类似地,由预读后台线程加载,然后仅访问一次的页面会移动到新页列表的头部。这些情况下,会将经常使用的页面推送到旧页的子列表中,从而导致它们会被淘汰。

而真正的热点数据,再次被访问时,InnoDB需要再次访问磁盘,将数据重新加载回缓冲区。

缓冲池配置

在上个小节,我们对缓冲池的LRU淘汰算法进行了了解,也发现了缓冲池对于特定场景下的一些问题,我们可以通过配置缓冲池的各个方面以提高性能。

1、理想情况下,可以将缓冲池的大小设置为尽可能大的值,从而无需过多的分页。缓冲池越大,InnoDB内存数据就越多,从磁盘读取数据一次,然后在后续读取期间从内存中访问数据。可以通过参数 innodb_buffer_pool_size 进行配置缓冲池大小。

2、在具有内存足够的64位系统上,可以将缓冲池拆分为多个部分,以最大限度地减少并发操作之间内存结构的争用。可以通过参数 innodb_buffer_pool_instances 进行配置,默认值为1。

3、可以将频繁访问的数据保留在内存中,避免上一节中提到的表扫描操作,会将大量不经常访问的数据带入缓冲池,而将频繁访问的数据淘汰掉。可以通过参数 innodb_old_blocks_time 进行配置,该配置用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的新页列表中去,默认是0。

4、可以控制何时、以及如何执行预读请求,以异步方式将页预读取到缓冲池中,可以通过参数 innodb_read_ahead_threshold 进行配置,默认值为56,关于innoDB的预读机制,这里暂不过多介绍。

5、可以控制何时发生后台刷新以及是否根据工作负载动态调整刷新率。

这里对于后台刷新的机制,需要进行简要说明,在LRU列表中的页被修改后,称该页为脏页(dirty page),即缓冲池中的页和磁盘上的数据产生了不一致的情况。此时数据库会通过CHECKPOINT机制将脏页刷回磁盘,而这个过程,是存储引擎在后台任务执行的操作。InnoDB对于此过程,采用自适应刷新机制,来控制刷新的速率,可以通过参数 innodb_adaptive_flushing 配置是否开启此功能,默认为ON,即开启。

更改缓冲区(change buffer)

更改缓冲区是一种特殊的数据结构,当这些页不在缓冲池中时,它会更改二级索引页。缓冲的变化,可能导致INSERT、UPDATE或者DELETE操作(DML)会被在这些页被其他读操作加载到缓冲池的操作后,合并执行。

更改缓冲区

在InnoDB引擎上进行插入操作时,一般需要按照主键顺序进行插入,这样才能获得较高的插入性能。

但是与主键聚簇索引不同,二级索引通常不是唯一的,并且插入二级索引的顺序相对随机。同样,删除和更新可能会影响不在索引树中相邻的二级索引页。

当受影响的页被其他操作读入缓冲池时,合并缓存的更改,避免了从磁盘读取二级索引页到缓冲池所需的大量随机访问I/O。

系统大部分空闲时或在慢速关闭期间运行的清除操作会定期将更新的索引页写入磁盘。与每个值立即写入磁盘相比,清除操作可以更有效地为一系列索引值写入磁盘块。

对表执行任何INSERT、UPDATE和DELETE操作时,索引列的值(特别是二级索引的值)通常按未排序顺序排列,需要大量I/O才能使二级索引更新。

更改缓冲区缓存了二级索引元素的改变,当其相关的页数据不在缓冲池中时,由此可以避免大量的I/O操作,不会立刻从硬盘中加载对应的页数据。

当页数据被加载到缓冲池中时,将合并缓冲的更改,稍后将更新的页刷新到磁盘。InnoDB主线程在服务器几乎空闲时以及在慢速关闭期间合并缓冲区的更改。

因为它可以减少磁盘读取和写入,所以更改缓冲区功能对于I/O绑定的工作负载最有价值,例如具有大量DML操作的应用程序(如批量插入)。

更改缓冲区配置

更改缓冲区占用缓冲池的一部分,从而减少了可用于缓存数据页的内存。如果工作集几乎适合缓冲池,或者数据表具有相对较少的二级索引,则禁用更改缓冲可能是更好的选择。如果工作数据集完全适合缓冲池,则更改缓冲不会产生额外开销,因为它仅适用于不在缓冲池中的页面。

InnoDB 使用 innodb_change_buffering 配置参数控制执行更改缓冲区的范围。可以为插入、删除(当索引记录最初标记为删除时)和清除操作(物理删除索引记录时)启用或禁用缓冲区。更新操作是插入和删除的组合。

默认 innodb_change_buffering 值为 all。

innodb_change_buffering 可配置参数如下:

  • all:默认值:缓冲区插入,删除标记操作和清除。
  • none:不要缓冲任何操作
  • inserts:缓冲插入操作
  • deletes:缓冲删除标记操作
  • changes:缓冲插入和删除标记操作
  • purges:缓冲在后台发生的物理删除操作。

自适应HASH索引

HASH是一种非常快的查找方法,在一般情况下,这种查找的时间复杂度为O(1)。即一般仅需要一次查找就能定位数据。而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为3-4层,因此需要3-4次查询。

InnoDB存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,称之为自适应哈希索引。自适应哈希索引是通过缓冲池的B+树页构造而来,因此建立的速度非常快,而且不需要对整张表构建哈希索引。InnoDB会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

其有一个要求,即对这个页的连续访问模式必须是一样的,也就是说其查询的条件(WHERE)必须完全一样,而且必须是连续的。

可以通过参数 innodb_adaptive_hash_index 配置指定是否开启自适应哈希索引,默认开启。

注意,InnoDB的索引类型是不可以指定哈希索引的,其哈希索引实现是自适应的,在内部自动构建。

日志缓冲区

日志缓冲区是保存要写入磁盘上日志文件的数据的内存区域,即redo log buffer。

当缓冲池中的页的版本比磁盘要新时,数据库需要将新版本的页从缓冲池刷新到磁盘。但是如果每次一个页发送变化,就进行刷新,那么性能开发是非常大的,于是InnoDB采用了Write Ahead Log策略,即当事务提交时,先写重做日志,然后再择时将脏页写入磁盘。如果发生宕机导致数据丢失,就通过重做日志进行数据恢复。

日志缓冲区大小由 innodb_log_buffer_size 变量定义,默认大小为16MB。日志缓冲区的内容会定期刷新到磁盘。大容量的日志缓冲区使大的事务能够运行,而无需在事务提交之前将重做日志数据写入磁盘。因此,如果有更新,插入或删除许多行的事务,则增加日志缓冲区的大小可以节省磁盘I/O。

innodb_flush_log_at_trx_commit 变量控制如何写入日志缓冲区的内容并刷新到磁盘。

该参数默认值为1,表示事务提交必须进行一次fsync操作,还可以设置为0和2。0表示事务提交时不进行写入重做日志操作,该操作只在主线程中完成,2表示提交时写入重做日志,但是只写入文件系统缓存,不进行fsync操作。由此可见,设置为0时,性能最高,但是丧失了事务的一致性。

innodb_flush_log_at_timeout 变量控制日志刷新频率。

结语

本篇,我们对InnoDB的架构有了大体的了解,对其内存架构的各部分组成进行了介绍,InnoDB的内存架构与磁盘架构是紧密相关的,因此下一篇中,我们将会继续介绍其磁盘架构的组成,敬请期待!

本篇参考:

MySQL技术内幕(InnoDB存储引擎)第2版

MySQL官方文档

MySQL探秘(三):InnoDB的内存结构和特性

更多精彩文章, 请关注我的个人公众号:老宣与你聊Java
让我们一起共同学习成长!
感谢您的支持!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值