0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

关于InnoDB的内存结构及原理详解

jf_f8pIz0xS 来源:SH的全栈笔记 作者:SH的全栈笔记 2021-04-16 16:15 次阅读

之前写过一篇文章「简单了解InnoDB原理」,现在回过头看,其实里面只是把缓冲池(Buffer Pool),重做日志缓冲(Redo Log Buffer)、插入缓冲(Insert Buffer)和自适应哈希索引Adaptive Hash Index)等概念简单的介绍了一下。

除此之外还聊了一下MySQL和InnoDB的日志,和两次写,总的来说算是一个入门级别的介绍,这篇文章就来详细介绍一下InnoDB的内存结构。

InnoDB内存结构

其大致结构如下图。

InnoDB内存的两个主要区域分别为Buffer Pool和Log Buffer,此处的Log Buffer目前是用于缓存Redo Log。而Buffer Pool则是MySQL或者说InnoDB中,十分重要、核心的一部分,位于主存。这也是为什么其访问数据的效率高,你可以暂时把它理解成Redis那样的内存数据库,因为我们更新和新增当然它不是,只是这样会更加方便我们理解。

Buffer Pool

通常来说,宿主机80%的内存都应该分配给Buffer Pool,因为Buffer Pool越大,其能缓存的数据就更多,更多的操作都会发生在内存,从而达到提升效率的目的。

由于其存储的数据类型和数据量非常多,Buffer Pool存储的时候一定会按照某些结构去存储,并且做了某些处理。否则获取的时候除了遍历所有数据之外,没有其他的捷径,这样的低效率操作肯定是无法支撑MySQL的高性能的。

因此,Buffer Pool被分成了很多页,这在之前的文章中也有讲过,这里不再赘述。每页可以存放很多数据,刚刚也提到了,InnoDB一定是对数据做了某些操作。

InnoDB使用了链表来组织页和页中存储的数据,页与页之间形成了双向链表,这样可以方便的从当前页跳到下一页,同时使用LRU(Least Recently Used)算法去淘汰那些不经常使用的数据。

同时,每页中的数据也通过单向链表进行链接。因为这些数据是分散到Buffer Pool中的,单向链表将这些分散的内存给连接了起来。

Log Buffer

Log Buffer用来存储那些即将被刷入到磁盘文件中的日志,例如Redo Log,该区域也是InnoDB内存的重要组成部分。Log Buffer的默认值为16M,如果我们需要进行调整的话,可以通过配置参数innodb_log_buffer_size来进行调整。

当Log Buffer如果较大,就可以存储更多的Redo Log,这样一来在事务提交之前我们就不需要将Redo Log刷入磁盘,只需要丢到Log Buffer中去即可。因此较大的Log Buffer就可以更好的支持较大的事务运行;同理,如果有事务会大量的更新、插入或者删除行,那么适当的增大Log Buffer的大小,也可以有效的减少部分磁盘I/O操作。

至于Log Buffer中的数据刷入到磁盘的频率,则可以通过参数innodb_flush_log_at_trx_commit来决定。

Buffer Pool的LRU算法

了解完了InnoDB的内存结构之后,我们来仔细看看Buffer Pool的LRU算法是如何实现将最近没有使用过的数据给过期的。

原生LRU

首先明确一点,此处的LRU算法和我们传统的LRU算法有一定的区别。为什么呢?因为实际生产环境中会存在全表扫描的情况,如果数据量较大,可能会将Buffer Pool中存下来的热点数据给全部替换出去,而这样就会导致该段时间MySQL性能断崖式下跌。

对于这种情况,MySQL有一个专用名词叫缓冲池污染。所以MySQL对LRU算法做了优化。

优化后的LRU

优化之后的链表被分成了两个部分,分别是 New Sublist 和 Old Sublist,其分别占用了 Buffer Pool 的3/4和1/4。

链表的前3/4,也就是 New Sublist 存放的是访问较为频繁的页,而后1/4也就是 Old Sublist 则是反问的不那么频繁的页。Old Sublist中的数据,会在后续Buffer Pool剩余空间不足、或者有新的页加入时被移除掉。

了解了链表的整体构造和组成之后,我们就以新页被加入到链表为起点,把整体流程走一遍。首先,一个新页被放入到Buffer Pool之后,会被插入到链表中 New Sublist 和 Old Sublist 相交的位置,该位置叫MidPoint。

该链表存储的数据来源有两部分,分别是:

MySQL的预读线程预先加载的数据

用户的操作,例如Query查询

默认情况下,由用户操作影响而进入到Buffer Pool中的数据,会被立即放到链表的最前端,也就是 New Sublist 的 Head 部分。但如果是MySQL启动时预加载的数据,则会放入MidPoint中,如果这部分数据被用户访问过之后,才会放到链表的最前端。

这样一来,虽然这些页数据在链表中了,但是由于没有被访问过,就会被移动到后1/4的 Old Sublist中去,直到被清理掉。

优化Buffer Pool的配置

在实际的生产环境中,我们可以通过变更某些设置,来提升Buffer Pool运行的性能。

例如,我们可以分配尽量多的内存给Buffer Pool,如此就可以缓存更多的数据在内存中

当前有足够的内存时,就可以搞多个Buffer Pool实例,减少并发操作所带来的数据竞争

当我们可以预测到即将到来的大量请求时,我们可以手动的执行这部分数据的预读请求

我们还可以控制Buffer Pool刷数据到磁盘的频率,以根据当前MySQL的负载动态调整

那我们怎么知道当前运行的 MySQL 中 Buffer Pool 的状态呢?我们可以通过命令show engine innodb status来查看。这个命令是看 InnoDB 整体的状态的, Buffer Pool 相关的监控指标包含在了其中,在Buffer Pool And Memory模块中。

样例如下。

---------------------- BUFFER POOL AND MEMORY ---------------------- Total large memory allocated 137428992 Dictionary memory allocated 972752 Buffer pool size 8191 Free buffers 4596 Database pages 3585 Old database pages 1303 Modified db pages 0 Pending reads 0 Pending writes: LRU 0, flush list 0, single page 0 Pages made young 1171, not young 0 0.00 youngs/s, 0.00 non-youngs/s Pages read 655, created 7139, written 173255 0.00 reads/s, 0.00 creates/s, 0.00 writes/s No buffer pool page gets since the last printout Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s LRU len: 3585, unzip_LRU len: 0 I/O sum[0]:cur[0], unzip sum[0]:cur[0]

解释一些关键的指标所代表的含义:

Total memory allocated:分配给 Buffer Pool 的总内存

Dictionary memory allocated:分配给 InnoDB 数据字典的总内存

Buffer pool size:分配给 Buffer Pool 中页的内存大小

Free buffers:分配给 Buffer Pool 中 Free List 的内存大小

Database pages:分配给 LRU 链表的内存大小

Old database pages:分配给 LRU 子链表的内存大小

Modified db pages:当前Buffer Pook中被更新的页的数量

Pending reads:当前等待读入 Buffer Pool 的页的数量

Pending writes LRU:当前在 LRU 链表中等待被刷入磁盘的脏页数量

都是些很常规的配置项,你可能会比较好奇什么是 Free List。

Free List 中存放的都是未被使用的页。因为MySQL启动的时候,InnoDB 会预先申请一部分页。如果当前页还未被使用,就会被保存在 Free List 中。

知道了 Free List,那么你也应该知道 Flush List,里面保存的是所有的脏页,都是被更改后需要刷入到磁盘的。

自适应哈希索引

自适应哈希索引(Adaptive Hash Index)是配合Buffer Pool工作的一个功能。自适应哈希索引使得MySQL的性能更加接近于内存服务器。

如果要启用自适应哈希索引,可以通过更改配置innodb_adaptive_hash_index来开启。如果不想启用,也可以在启动的时候,通过命令行参数--skip-innodb-adaptive-hash-index来关闭。

自适应哈希索引是根据索引Key的前缀来构建的,InnoDB 有自己的监控索引的机制,当其检测到为当前某个索引页建立哈希索引能够提升效率时,就会创建对应的哈希索引。如果某张表数据量很少,其数据全部都在Buffer Pool中,那么此时自适应哈希索引就会变成我们所熟悉的指针这样一个角色。

当然,创建、维护自适应哈希索引是会带来一定的开销的,但是比起其带来的性能上的提升,这点开销可以直接忽略不计。但是,是否要开启自适应哈希索引还是需要看具体的业务情况的,例如当我们的业务特征是有大量的并发Join查询,此时访问自适应哈希索引被产生竞争。并且如果业务还使用了LIKE或者%等通配符,根本就不会用到哈希索引,那么此时自适应哈希索引反而变成了系统的负担。

所以,为了尽可能的减少并发情况下带来的竞争,InnoDB对自适应哈希索引进行了分区,每个索引都被绑定到了一个特定的分区,而每个分区都由单独的锁进行保护。其实通俗点理解,就是降低了锁的粒度。分区的数量我们可以通过配置innodb_adaptive_hash_index_parts来改变,其可配置的区间范围为[8, 512]。

Change Buffer

聊完了 Buffer Pool 中索引相关,剩下的就是 Change Buffer 了。Change Buffer是一块比较特殊的区域,其作用是用于存储那些当前不在 Buffer Pool 中的但是又被修改过的二级索引。

用流程来描述一下就是,当我们更新了非聚簇索引(二级索引)的数据时,此时应该是直接将其在Buffer Pool中的对应数据更新了即可,但是不凑巧的是,当前二级索引不在 Buffer Pool 中,此时将其从磁盘拉取到 Buffer Pool 中的话,并不是最优的解,因为该二级索引可能之后根本就不会被用到,那么刚刚昂贵的磁盘I/O操作就白费了。

所以,我们需要这么一个地方,来暂存对这些二级索引所做的改动。当被缓存的二级索引页被其他的请求加载到了Buffer Pool 中之后,就会将 Change Buffer 中缓存的数据合并到 Buffer Pool 中去。

当然,Change Buffer也不是没有缺点。当 Change Buffer 中有很多的数据时,全部合并到Buffer Pool可能会花上几个小时的时间,并且在合并的期间,磁盘的I/O操作会比较频繁,从而导致部分的CPU资源被占用。

那你可能会问,难道只有被缓存的页加载到了 Buffer Pool 才会触发合并操作吗?那要是它一直没有被加载进来,Change Buffer 不就被撑爆了?很显然,InnoDB在设计的时候考虑到了这个点。除了对应的页加载,提交事务、服务停机、服务重启都会触发合并。
编辑:lyn

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    2763

    浏览量

    72744
  • MySQL
    +关注

    关注

    1

    文章

    775

    浏览量

    25997
  • 索引
    +关注

    关注

    0

    文章

    59

    浏览量

    10408
收藏 人收藏

    评论

    相关推荐

    FreeRTOS内存机制详解

    FreeRTOS是一种实时操作系统,它提供了多种内存分配方式,包括动态内存分配和静态内存分配。
    的头像 发表于 12-31 16:49 1100次阅读
    FreeRTOS<b class='flag-5'>内存</b>机制<b class='flag-5'>详解</b>

    详解内存条和内存颗粒

    在80286时代,内存颗粒(Chip)是直接插在主板上的,叫做DIP(Dual In-line Package)。到了80386时代,换成1片焊有内存颗粒的电路板,叫做SIMM
    的头像 发表于 12-16 15:00 1449次阅读
    <b class='flag-5'>详解</b><b class='flag-5'>内存</b>条和<b class='flag-5'>内存</b>颗粒

    jvm内存模型和内存结构

    JVM(Java虚拟机)是Java程序的运行平台,它负责将Java程序转换成机器码并在计算机上执行。在JVM中,内存模型和内存结构是两个重要的概念,本文将详细介绍它们。 一、JVM内存
    的头像 发表于 12-05 11:08 392次阅读

    PCI5565反射内存卡系统结构与使用

    反射内存卡系统结构与使用 反射内存卡系统结构与使用 反射内存卡基于 PCI 接口,是反射内存实时
    的头像 发表于 11-29 14:45 321次阅读

    详解高密 PCB走线布线的垂直导电结构 (VeCS)

    详解高密 PCB走线布线的垂直导电结构 (VeCS)
    的头像 发表于 11-28 17:00 508次阅读
    <b class='flag-5'>详解</b>高密 PCB走线布线的垂直导电<b class='flag-5'>结构</b> (VeCS)

    Linux内核的内存管理详解

    内存管理的主要工作就是对物理内存进行组织,然后对物理内存的分配和回收。但是Linux引入了虚拟地址的概念。
    发表于 08-31 14:46 425次阅读
    Linux内核的<b class='flag-5'>内存</b>管理<b class='flag-5'>详解</b>

    C语言中数组和结构体的内存表示和布局

    C语言中,数组和结构体都可以代表一块内存,但为什么结构体可以直接赋值,而数组不可以?这个问题涉及到C语言的设计哲学、语法规则以及内存布局的细节。本文将深入探讨这些问题,通过原理介绍和举
    发表于 08-28 10:54 467次阅读

    Linux中内存管理子系统开发必知的3个结构概念

    Linux中内存管理子系统使用节点(node)、区域(zone)和页(page)三级结构描述物理内存
    的头像 发表于 08-28 09:34 570次阅读
    Linux中<b class='flag-5'>内存</b>管理子系统开发必知的3个<b class='flag-5'>结构</b>概念

    Linux内核的物理内存组织结构详解

    Linux中内存管理子系统使用 节点(node)、区域(zone)和页(page) 三级结构描述物理内存
    发表于 08-21 15:35 245次阅读
    Linux内核的物理<b class='flag-5'>内存</b>组织<b class='flag-5'>结构</b><b class='flag-5'>详解</b>

    Armv8-A内存定序模型详解

    ,如启动代码或驱动器;对于为多读应用程序或共享内存系统写入代码的任何人都特别相关;本指南末尾,您可以检查您的知识;在开始之前,本指南假定您熟悉武器内存类型;如果不熟悉,则在 Armv8-A 记忆模型指南中读取关于设备
    发表于 08-02 11:03

    Armv8-A体系结构中的内存系统详解

    本指南介绍Armv8-A体系结构中的内存系统。这些系统详细通过内存模型、内存类型、内存属性和屏障。 在以下情况下,您必须了解
    发表于 08-02 10:38

    一文详解C语言内存管理

    C语言内存管理指对系统内存的分配、创建、使用这一系列操作。
    发表于 07-26 16:04 475次阅读
    一文<b class='flag-5'>详解</b>C语言<b class='flag-5'>内存</b>管理

    MySQL为什么选择B+树作为索引结构

    在MySQL中,无论是Innodb还是MyIsam,都使用了B+树作索引结构(这里不考虑hash等其他索引)。本文将从最普通的二叉查找树开始,逐步说明各种树解决的问题以及面临的新问题,从而说明MySQL为什么选择B+树作为索引结构
    的头像 发表于 07-20 11:28 501次阅读
    MySQL为什么选择B+树作为索引<b class='flag-5'>结构</b>?

    ARM体系结构内存序与内存屏障

    本文介绍 Armv8-A 架构的内存序模型,并介绍 arm 的各种内存屏障。本文还会指出一些需要明确内存保序的场景,并指明如何使用内存屏障以让程序运行正确。
    发表于 06-15 18:19 950次阅读
    ARM体系<b class='flag-5'>结构</b>之<b class='flag-5'>内存</b>序与<b class='flag-5'>内存</b>屏障

    JVM内存布局详解

    JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的稳定高效运行。不同的JVM对于内存的划分方式和管理机制存在部分差异。结合JVM虚拟机规范,一起来探讨jVM的
    的头像 发表于 04-26 10:10 342次阅读
    JVM<b class='flag-5'>内存</b>布局<b class='flag-5'>详解</b>