首页 > 系统相关 >InnoDB 内存结构

InnoDB 内存结构

时间:2024-03-31 21:34:04浏览次数:32  
标签:缓存 buffer young 链表 InnoDB 内存 LRU pool 结构

参考资料

https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html

https://relph1119.github.io/mysql-learning-notes/#/mysql/

InnoDB 内存结构

内存结构如下图

查看InnoDB运行状态

SHOW ENGINE INNODB STATUS

通过上述命令查看InnoDB运行时的状态信息

Buffer Pool

buffer pool是主内存中的一个区域,在其中,InnoDB可以缓存访问到的表和索引数据。通过缓存访问频繁的数据页和索引页,可以加快数据的读、写性能。

InnoDB中所有数据页的读写操作都需要通过buffer pool进行

  • Innodb 读操作,先从buffer_pool中查看数据的数据页是否存在,如果不存在,则将page从磁盘读取到buffer pool中;存在则直接从buffer pool中读取。
  • Innodb 写操作,先把数据和日志写入 buffer pool 和 log buffer,再由后台线程以一定频率将 buffer 中的内容刷到磁盘。

缓冲池的大小是影响 InnoDB 性能的关键因素,在专用服务器上,通常会将高达80%的物理内存分配给缓冲池。较大的缓冲池可以容纳更多经常访问的数据,从而减少磁盘 I/O 并提高查询性能。通过参数innodb_buffer_pool_size控制,默认是128MB。

buffer pool内部组成

Buffer Pool 中默认的缓存页大小和在磁盘上默认的页大小是一样的,都是 16KB 。为了更好的管理Buffer Pool 中的缓存页,每一个缓存页都有一个控制信息块 ,这些控制信息包括该页所属的表空间编号、页号、缓存页在 Buffer Pool 中的地址、链表节点信息、一些锁信息以及 LSN 信息。

Free 链表

从磁盘上加载数据页到buffer pool中时,应该如何进行内存分配呢?怎么知道buffer pool中,那些缓存页已经使用,那些缓存页是空闲可分配的?

这些工作由Free链表完成,Free链表负责记录buffer pool中可用的缓存页,这个过程由通过将缓存页的控制块作为Free链表中的一个节点完成。

从图中可以看出,Free链表中定义了一个基节点 ,包含着链表的头节点地址,尾节点地址,以及链表中节点的数量等信息。在刚刚初始化的buffer pool中,缓存页都是空闲的,缓存页对应的控制块都会被添加Free链表中。

Free链表创建完之后,每当需要从磁盘中加载一个页到Buffer Pool中时,就从free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上(该页所在的表空间、页号),然后把该缓存页对应的 free链表节点从链表中移除。

Flush链表

在进行更新操作时,只修改buffer pool中的缓存页,而不是直接落盘。修改后的缓存页叫做脏页,与磁盘上的数据不一致。后台线程会定期的把脏页刷新到磁盘上,这就需要先对脏页做一个管理,可以查找到buffer pool中的脏页。对应的管理就是Flush链表,每当缓存页被修改后,就将其对应的控制块作为一个节点加入Flush链表。

LRU链表

Buffer Pool 中内存是有限的,如果需要缓存的数据页占用的内存大小超过了 Buffer Pool 大小,也就 是 free链表 中已经没有多余的空闲缓存页。在这种时候,就需要将一些不常使用的缓存页进行回收,只保留经常使用的缓存页。

简单LRU链表

如何实现回收不常使用的数据,而保留最近使用的数据呢?那自然是创建一个LRU链表进行缓存页的管理了。

LRU链表处理逻辑为:

  • 在将数据页从磁盘加载到 Buffer Pool中时,把该数据页对应的控制块作为节点放到LRU链表的头部。
  • 如果数据页已经缓存在Buffer Pool中,每次访问时把数据页对应的控制块移动到LRU链表的头部。

也就是说,只要我们使用到某个缓存页,就把该缓存页调整到LRU链表的头部,这样LRU链表尾部就是最近最少使用的缓存页。当Buffer Pool中的空闲缓存页使用完时,到LRU链表的尾部找些缓存页淘汰即可释放内存。

分区域LRU

但如果只是简单LRU链表的话,在某些情况下,可能导致buffer pool中缓存命中率偏低。

例如,在进行全表扫描时,会将该表所有的数据页加载到buffer pool中,在数据足够时,会直接将buffer pool填满。并且这些数据全部被加载到LRU链表的头部,在进行回收时,会将buffer pool中之前的数据页全部被删除。这严重影响到了其他查询对buffer pool的使用,降低了缓存命中率。

于是乎,分区域的LRU来了。InnoDB中,将LRU链表分为两个区域:

  • 一部分存储使用频率非常高的缓存页,所以这一部分链表也叫做 热数据 ,或者称 young区域 。
  • 一部分存储使用频率不是很高的缓存页,所以这一部分链表也叫做 冷数据 ,或者称 old区域 。

young区域和old区域按比例进行划分,默认情况下,old区域占整个LRU链表的37%。可以通过系统参数innodb_old_blocks_pct 进行配置。

在将LRU链表分区域后,优化处理逻辑如下

当磁盘上的数据页初次加载到Buffer Pool中时,该缓存页对应的控制块会被放到old区域的头部,并且在第一次访问该缓存页后,不会移动到young区域。

只有在对该缓存页进行两次及以上访问,并且访问间隔时间大于1s时,才将其放到young区域。

数据页哈希

如何判断数据页是否已经被加载到buffer pool中呢?我们知道一个数据页由表空间号和页号唯一标识,所以在buffer pool中可以通过唯一标识进行查找。

最终的方法是,通过数据页的表空间号和页号计算哈希值,然后哈希查找加快查找速度。

脏页的刷新

后台线程会定期的将buffer pool中的脏页刷新到磁盘上,这个操作叫做Checkpoint,主要有两种刷新路径:

  • 从 Flush链表中刷新:后台线程定时从flush链表中刷新一部分页面到磁盘,刷新的速率取决于系统负载量。
  • 从LRU链表中刷新:在通过LRU链表回收缓存页时,如果回收的缓存页是脏页数据,则将其刷新到磁盘。

多个buffer pool实例

Buffer Pool本质是InnoDB向操作系统申请的一块连续的内存空间,在并发环境下,更新Buffer Pool中的各种链表需要加锁进行并发控制。在 Buffer Pool 空间大且并发量高的情况下, 单一的 Buffer Pool 可能会影响请求的处理速度。

所以在空间足够的情况下,InnoDB默认有8个buffer pool实例,空间足够的条件是buffer pool内存大于1GB,否则只有一个实例。可以通过参数innodb_buffer_pool_instances指定buffer pool实例数量。

buffer pool实例之间是独立的,独立的申请内存空间、独立管理其中的链表信息等。

查看运行时的Buffer Pool状态

SHOW ENGINE INNODB STATUS

其中,BUFFER POOL相关的信息如下

BUFFER POOL AND MEMORY
----------------------
Total memory allocated 13218349056;
Dictionary memory allocated 4014231
Buffer pool size 786432
Free buffers 8174
Database pages 710576
Old database pages 262143
Modified db pages 124941
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 6195930012, not young 78247510485
108.18 youngs/s, 226.15 non-youngs/s
Pages read 2748866728, created 29217873, written 4845680877
160.77 reads/s, 3.80 creates/s, 190.16 writes/s
Buffer pool hit rate 956 / 1000, young-making rate 30 / 1000 not 605 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 710576, unzip_LRU len: 118
I/O sum[134264]:cur[144], unzip sum[16]:cur[0]

字段含义解释

  • Total memory allocated :代表 Buffer Pool 向操作系统申请的连续内存空间大小,包括全部控制块、缓

存页、以及碎片的大小。

  • Dictionary memory allocated :为数据字典信息分配的内存空间大小,注意这个内存空间和 Buffer Pool

没啥关系,不包括在 Total memory allocated 中。

  • Buffer pool size :代表该 Buffer Pool 可以容纳多少缓存 页 ,注意,单位是 页 !
  • Free buffers :代表当前 Buffer Pool 还有多少空闲缓存页,也就是 free链表 中还有多少个节点。
  • Database pages :代表 LRU 链表中的页的数量,包含 young 和 old 两个区域的节点数量。
  • Old database pages :代表 LRU 链表 old 区域的节点数量。
  • Modified db pages :代表脏页数量,也就是 flush链表 中节点的数量。
  • Pending reads :正在等待从磁盘上加载到 Buffer Pool 中的页面数量。当准备从磁盘中加载某个页面时,会先为这个页面在 Buffer Pool 中分配一个缓存页以及它对应的控制块,然后把这个控制块添加到 LRU 的 old 区域的头部,但是这个时候真正的磁盘页并没有被加载进来, Pending reads 的值会跟着加1。
  • Pending writes LRU :即将从 LRU 链表中刷新到磁盘中的页面数量。
  • Pending writes flush list :即将从 flush 链表中刷新到磁盘中的页面数量。
  • Pending writes single page :即将以单个页面的形式刷新到磁盘中的页面数量。
  • Pages made young :代表 LRU 链表中曾经从 old 区域移动到 young 区域头部的节点数量。这里需要注意,一个节点每次只有从 old 区域移动到 young 区域头部时才会将 Pages made young 的值加1,也就是说如果该节点本来就在 young 区域,由于它符合在 young 区域1/4后边的要求,下一次访问这个页面时也会将它移动到 young 区域头部,但这个过程并不会导致 Pages made young 的值加1。
  • Page made not young :在将 innodb_old_blocks_time 设置的值大于0时,首次访问或者后续访问某个处在 old 区域的节点时由于不符合时间间隔的限制而不能将其移动到 young 区域头部时, Page made not young 的值会加1。这里需要注意,对于处在 young 区域的节点,如果由于它在 young 区域的1/4处而导致它没有被移动到 young 区域头部,这样的访问并不会将 Page made not young 的值加1。
  • youngs/s :代表每秒从 old 区域被移动到 young 区域头部的节点数量。
  • non-youngs/s :代表每秒由于不满足时间限制而不能从 old 区域移动到 young 区域头部的节点数量。
  • Pages read 、 created 、 written :代表读取,创建,写入了多少页。后边跟着读取、创建、写入的速率。
  • Buffer pool hit rate :表示在过去某段时间,平均访问1000次页面,有多少次该页面已经被缓存到Buffer Pool 了。
  • young-making rate :表示在过去某段时间,平均访问1000次页面,有多少次访问使页面移动到 young 区域的头部了。需要大家注意的一点是,这里统计的将页面移动到 young 区域的头部次数不仅仅包含从 old 区域移动到young 区域头部的次数,还包括从 young 区域移动到 young 区域头部的次数(访问某个 young 区域的节点,只要该节点在 young 区域的1/4处往后,就会把它移动到 young 区域的头部)。not (young-making rate) :表示在过去某段时间,平均访问1000次页面,有多少次访问没有使页面移动到 young 区域的头部。需要注意的一点是,这里统计的没有将页面移动到 young 区域的头部次数不仅仅包含因为设置了innodb_old_blocks_time 系统变量而导致访问了 old 区域中的节点但没把它们移动到 young 区域的次数,还包含因为该节点在 young 区域的前1/4处而没有被移动到 young 区域头部的次数。
  • LRU len :代表 LRU链表 中节点的数量。
  • unzip_LRU :代表 unzip_LRU链表 中节点的数量(由于我们没有具体唠叨过这个链表,现在可以忽略它的值)。
  • I/O sum :最近50s读取磁盘页的总数。
  • I/O cur :现在正在读取的磁盘页数量。
  • I/O unzip sum :最近50s解压的页面数量。
  • I/O unzip cur :正在解压的页面数量。

Change Buffer

change buffer是一种特殊的数据结构,专用于提高数据新增和更新时的性能,是buffer pool中的一部分。

change buffer默认为buffer pool大小的25%,最大可配置为50%。可以通过innodb_change_buffer_max_size配置

  • 当数据库主要用于查询时,减少change buffer的大小
  • 当数据库中新增、更新较多时,增加change buffer的大小

https://dev.mysql.com/doc/refman/5.7/en/innodb-change-buffer.html

适用场景

在事务中,存在对二级非唯一索引数据的新增、更新时

  • 如果对应的数据页在buffer pool中,直接更新buffer pool中的数据页。
  • 如果对应的数据页不在buffer pool中,将更改信息写入change buffer,信息为在某某数据页(通过表空间号和页号)对某行记录进行了某某更改。后续处理逻辑为
    • 其它读取请求将对应的数据页加载到buffer pool时,将change buffer中的更改合并到其中,然后该数据页被标记为脏页,等待后续落盘。
    • 后台刷新,purge操作会在change buffer空间不足、系统空闲或者缓慢关闭时,加载相应的二级索引页到buffer pool中,然后将change buffer中的更改合并到buffer pool中。

为什么是二级索引(非唯一)?

二级索引在进行新增、更新时,索引值的查询位置往往是随机的。而这可能会引起索引中记录的移动(因为索引结构是颗B+树,需要保证平衡),从而导致更多的磁盘IO访问。

而要提升数据库的处理速度和并发性能,就得尽量减少磁盘IO访问。通过change buffer进行延时更新,便可以达到提高处理速度和并发性能。

为什么不是唯一索引?

因为主键和唯一索引有唯一性约束,需要将数据加载到内存中进行唯一性校验,不适用于change buffer。

Log Buffer

Log buffer是InnoDB内存中的一个区域,专门用于管理redo log。默认大小是16MB,可以通过innodb_log_buffer_size进行配置。更大的log buffer可以保证在一个大事务中不需要在事务提交之前将redo log写入磁盘上。如果有大量的update、insert或者delete操作,应该增加log buffer的大小。

标签:缓存,buffer,young,链表,InnoDB,内存,LRU,pool,结构
From: https://www.cnblogs.com/cd-along/p/18107315

相关文章

  • Ray Tracking 加速结构
    基本原理中使用AABB作为判断光线和物体相交的加速。在AABB内部如何快速判断判断光线和物体的相交情况呢?主要分为种方法:UniformgridsSpatialpartitions注意这里使用的加速结构是在光线追踪之前做的准备工作。Grids分格子,然后记住每个格子里有哪些物体。碰到格子的话,再和......
  • InnoDB 数据页
    参考资料https://relph1119.github.io/mysql-learning-notes/#/mysql/我们知道InnoDB管理存储空间的基本单位是页,一个页的大小默认是16KB。InnoDB为了不同的目的而设计了许多种不同类型的页,如changebufferpage、undologpage、indexpage。其中,IndexPage就是用于存放数......
  • linux内存管理2
    1.LVM:管理磁盘的一种方式,与基本磁盘无异。  特点:随意扩张大小2.PV:将物理磁盘转变为物理卷     pvcreate+空格文件磁盘名(创建物理卷)     pvs(查看物理卷)  VG:卷组,里面有多个物理卷组成     vgcreate+空格卷组名++空格+磁盘名 ......
  • Java(3) ----- File类、IO流、基本软件结构
    File类:代表当前操作系统的文件对象。File类:建用来操作操作系统的文件对象的,删除文件,获取文件信息,创建文件(文件夹)广义来说操作系统认为文件包含(文件和文件夹)File类创建文件路径:相对路径:默认是直接相对到工程目录下寻找文件的;分隔符:File:separatorgetAbsolutePath()绝对......
  • 数据结构(六)——图的遍历
    6.3图的遍历6.3.1图的广度优先遍历⼴度优先遍历(Breadth-First-Search,BFS)要点:1.找到与⼀个顶点相邻的所有顶点2.标记哪些顶点被访问过3.需要⼀个辅助队FirstNeighbor(G,x):求图G中顶点x的第⼀个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1。Next......
  • 数据结构 - 线性表 - 顺序表
    前言最近刚刚开始复习408数据结构,就想着把每个章节的代码部分汇总记录一下,写成一组文章,便于日后的复习和参考。本系列整体的框架和课后习题参考了王道的复习指导。在本系列的每篇文章中,我会先把该章节所对应数据结构的基础代码放上来,然后附上参考资料习题对应的题目代码。线性......
  • C++初阶篇----内存管理
    目录引言1.内存分布2.C动态内存管理方式:malloc/calloc/realloc/free3.C++动态内存管理:new和delete3.1内置类型3.2自定义类型4.operatornew与operatordelete函数4.1operatornew与operatordelete函数5.new和delete的实现底层5.1内置类型5.2自定义类型引......
  • 对于结构体的见解(新手)
    不知道还能坚持多久,是没有自信了还是?不试试怎麽知道?希望自己以后可以挣好多好多钱,不要在为钱发愁!!!加油吧,少年!!!首先结构体是自定义类型的,它里面包含许多成员变量,  结构体内存对齐对齐规则 vs中的默认对齐数为8    修改默认对齐数 intb[1000]; 位段......
  • C生万物之循环结构全面学习<四>
    万水千山总是情,点点关注行不行。一声朋友一生情,点点关注才能行!文章目录1.三种循环结构1_1while循环if和while对比while执行流程实战示例1_2for循环for循环执行流程for循环实战while循环和for循环对比1_3do-while循环do-while循环执行流程2_1break和continue语句wh......
  • 操作系统体系结构
    操作系统体系结构时钟管理:利用时钟中断实现计时功能原语:原语是一种特殊的程序,具有原子性。也就是说,这段程序的运行必须一气呵成,不可被“中断”非内核功能:Ubuntu、Centos的开发团队,其主要工作是实现非内核功能,而内核都是用了Linux内核内核内核是操作系统最基木,最核心的......