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

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

    关注

    9

    文章

    3258

    浏览量

    76597
  • MySQL
    +关注

    关注

    1

    文章

    939

    浏览量

    29855
  • 索引
    +关注

    关注

    0

    文章

    60

    浏览量

    10869
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    260Pin DDR4 2133 SO-DIMM 4GB内存模块:技术详解与应用指南

    260Pin DDR4 2133 SO-DIMM 4GB内存模块:技术详解与应用指南 引言 在当今的电子产品中,内存模块扮演着至关重要的角色,它直接影响着系统的性能和响应速度。今天,我将为大家详细
    的头像 发表于 05-13 12:15 199次阅读

    探索ADVANTECH AQD - D4U4GN24 - HP DDR4内存模块:性能与参数详解

    探索ADVANTECH AQD - D4U4GN24 - HP DDR4内存模块:性能与参数详解 在当今数字化时代,内存模块作为计算机系统的关键组件,其性能直接影响着系统的运行效率
    的头像 发表于 05-13 09:15 427次阅读

    LuatOS的内存分配机制

    不同 LuatOS 硬件平台在内存布局上存在差异,Lua 运行内存、系统内存与 PSRAM 扩展内存的分配方式各有区别。部分型号将系统与脚本内存
    的头像 发表于 04-16 12:37 225次阅读
    LuatOS的<b class='flag-5'>内存</b>分配机制

    DDR5内存断崖式下跌或引发踩踏式抛货?

    内存
    芯广场
    发布于 :2026年03月30日 15:35:23

    MangoTree Halo Ultra「全新PXI」,标配自动纠错内存#

    内存
    芒果树数字
    发布于 :2026年03月06日 15:59:34

    不同于HBM垂直堆叠,英特尔新型内存ZAM技术采用交错互连拓扑结构

    不同于HBM垂直堆叠,英特尔新型内存ZAM技术采用交错互连拓扑结构   据日本媒体PCWatch报道,英特尔在2026年日本英特尔连接大会(Intel Connection Japan 2026)上
    的头像 发表于 02-11 11:31 2139次阅读
    不同于HBM垂直堆叠,英特尔新型<b class='flag-5'>内存</b>ZAM技术采用交错互连拓扑<b class='flag-5'>结构</b>

    Linux内核伙伴系统内存申请函数详解:从原理到实战

    在 Linux 内核中,内存管理是整个系统稳定运行的基石,而伙伴系统(Buddy System) 作为内核物理内存分配的核心机制,更是驱动开发、内核模块开发的必备知识点。它通过 "2 的幂
    的头像 发表于 02-10 16:58 3771次阅读
    Linux内核伙伴系统<b class='flag-5'>内存</b>申请函数<b class='flag-5'>详解</b>:从原理到实战

    内存条崩了?涨了十倍的内存颗粒卖给谁?内存芯片接下来怎么玩?

    内存
    芯广场
    发布于 :2026年01月26日 17:48:21

    keil中c语言的动态分配内存

    关于内存的错误往往都是毁灭性的。下面我列举几点最容易犯的错误,希望能帮助到大家。 (1)对空指针(NULL)解引用。看下面的代码: 这段代码看上去似乎没什么错误,但存在潜在的风险:由于没有对指针p
    发表于 01-21 06:04

    内存持续涨价,多重信号预警成本压力#内存芯片#涨价#行业#消费电子#AI#

    内存芯片
    jf_15747056
    发布于 :2026年01月08日 18:38:35

    rk基于linux/android内存管理

    一、内存分布   U-Boot 由前级 Loader 加载到 CONFIG_SYS_TEXT_BASE 地址,初始化时会探明当前系统的总内存容 量, 32 位平台上认为最大 4GB 可用(但是不影响
    的头像 发表于 12-15 10:42 343次阅读
    rk基于linux/android<b class='flag-5'>内存</b>管理

    Linux Swap交换空间详解:Android编译内存不足?这样扩充立竿见影

        在  Linux  系统使用过程中,你是否遇到过  “ 内存不足 ”  的报错?比如编译  Android  源码时,明明按教程操作,却因物理内存没达到  16G  要求而编译中断?这正是
    的头像 发表于 12-06 08:10 4706次阅读

    Mysql数据恢复—Windows Server下MySQL(InnoDB)全表误删数据恢复案例

    本地服务器,操作系统为windows server。服务器上部署mysql单实例,innodb引擎,独立表空间。未进行数据库备份,未开启binlog。 人为误操作使用Delete命令删除数据时未添加where子句,导致全表数据被删除。删除后未对该表进行任何操作。需要恢复误删除的数据。
    的头像 发表于 09-23 15:56 923次阅读
    Mysql数据恢复—Windows Server下MySQL(<b class='flag-5'>InnoDB</b>)全表误删数据恢复案例

    科普:什么AI 内存技术

    AI 内存是一种专为人工智能 (AI) 应用设计的新型内存技术。与传统的通用内存(如 DDR5 或 LPDDR5)不同,AI 内存的核心目标是解决 AI 计算中遇到的两大挑战:带宽瓶颈
    的头像 发表于 09-03 15:44 1781次阅读

    SSH常用命令详解

    SSH常用命令详解
    的头像 发表于 06-04 11:30 2274次阅读