当前位置: 首页 > news >正文

[MySQL数据库] InnoDB存储引擎(三): 内存结构详解

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:
🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection与数据结构 (93平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(97平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(95平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均质量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均质量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均质量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

目录

  • 1. InnoDB存储引擎中内存结构的组成部分有哪些?
  • 2. 为什么需要内存结构?
  • 3. 缓冲池-Buffer Pool
    • 3.1 缓冲池的作用
    • 3.2 缓冲池是如何组织数据的?
      • 3.2.1 缓冲池的结构是怎样的?
      • 3.2.2 缓冲池中页与页之间是如何建立连接的?
      • 3.2.3 衍生问题1: 内存中的数据页与磁盘上的数据页是什么关系
      • 3.2.4 衍生问题4: Buffer Pool的大小可以设置吗?
      • 3.2.5 衍生问题3: chunk的作用是什么?
      • 3.2.6 控制块与Page是如何初始化的?
      • 3.2.7 可以通过缓冲池来提升性能吗?
    • 3.3 缓冲池中的页是如何进行管理的?
      • 3.3.1 衍生问题1: 内存中有这么多数据页如何快速找到目标页
      • 3.3.2 衍生问题2: 缓冲池中的数据放不下怎么办?
    • 3.4 缓冲池采用哪种淘汰策略?是如何实现的?
      • 3.4.1 为什么要把页插入到中间而不是直接插入到新子列表的头部?
  • 4. 变更缓冲区
    • 4.1 变更缓冲区的作用?
      • 4.1.1 衍生问题: 为什么是二级索引?
    • 4.2 变更缓冲区的主要配置项有哪些?
  • 5. 自适应哈希索引
    • 5.1 自适应哈希索引的作用
      • 5.1.1 衍生问题1: 为什么要建立自适应哈希索引?
      • 5.1.2 衍生问题2: 自适应哈希索引保存在哪里?
  • 6. 日志缓冲区
    • 6.1 日志缓冲区的作用
      • 6.1.1 衍生问题1: 日志不通过Log Buffer直接写入磁盘不行吗?
      • 6.1.2 衍生问题2: Log Buffer与日志文件是如何配合工作的?

1. InnoDB存储引擎中内存结构的组成部分有哪些?

InnoDB存储引起中内存结构主要分为:

  • Buffer Pool缓冲池
  • Change Buffer变更缓冲区
  • adaptive_hash_index自适应哈希索引
  • Log Buffer日志缓冲区
    在这里插入图片描述

2. 为什么需要内存结构?

这个问题在InnoDB架构章节已经做了一些解释,从MySQL实现的角度来思考这个问题,数据库的作用就是保存数据,用户真实的数据最终都会保存在磁盘上,在查询数据的过程中,如果每次都从磁盘上读取会严重影响效率,为了提高数据的访问效率,InnoDB会把查询到的数据缓存到缓存中,当再查询时,如果目标数据已经存在于内存中,就可以从内存中直接读取,从而大幅提升效率.
也就是说磁盘结构中的文件是用来保存数据实现数据持久化的,内存结构是用来缓存数据提升效率的.

3. 缓冲池-Buffer Pool

3.1 缓冲池的作用

缓冲池在内存结构中的位置如下图所示
在这里插入图片描述

  • 缓冲池主要用来缓存被访问的InnoDB表数据页和索引数据页,是主内存中的一篇区域,允许直接从内存中访问频繁使用的数据从而提升效率.在专用数据库服务器上,通常会将多达80%的物理内存分配给缓冲池.
  • 其次缓存池中不仅仅缓存了磁盘的数据页,页存储了锁信息,Change Buffer信息,Adaptive Hash index,double write Buffer等信息.

3.2 缓冲池是如何组织数据的?

3.2.1 缓冲池的结构是怎样的?

  • 从缓冲池的概念了解到他是主内存中的一片区域,在专用服务器上会将多达80%的物理内存分配给缓冲池,在这么大的内存空间中如何保证效率就是要解决的问题.
  • 缓冲池采用与表空间类似的方式对数据进行组织,如下图所示:
    • 缓冲池中包含至少一个Instance实例,Instance是真正的缓冲池的实例对象,内存操作都是在Instances中进行的.
    • 每个Instances中包含至少一个chunk块,chunk是在服务器运行状态下动态调用缓冲池进行大小时操作的块大小.
    • 每个块中包含和管理若干个从磁盘加载到内存的Page数据页.
      在这里插入图片描述
  • 可以看出缓冲池通过定义不同的数据结构,但最终管理的是每个数据页,这些数据页是从磁盘加载到内存中的,也就是说磁盘中的数据页加载到内存中之后,对应的就是内存中的数据页,并且页与页之间用链表连接.
  • 那么这时候就有一个问题,我们知道磁盘中的每个数据页大小默认是16KB,并且行与行之间通过头信息中的next_record记录下一行的地址偏移量,在页结构定义中并没有一个字段用来表示内存中下一页的地址,那么在内存中如何为每个页建立连接呢?

3.2.2 缓冲池中页与页之间是如何建立连接的?

  • 由于数据页中没有一个字段用来表示下一页的地址,为了每个数据页在内存中实现表连接,InnoDB定义了一个叫"控制块"的数据结构,"控制块"中有三个重要信息分别是:
    • 指向数据页的内存地址
    • 前一个控制块的内存地址
    • 后一个控制块的内存地址
  • 之后再用一个双向链表管理每个控制块,如下图所示:
    在这里插入图片描述
  • 为了确定控制块链表的起始位置,专门定义了一个头结点,头结点中包含了三个主要信息,如图所示:
    • 第一个控制块的内存地址
    • 最后一个控制块的内存地址
    • 链表中控制块的数量
  • 通过遍历控制块链表就可以遍历内存中的数据页.
  • 总结: 缓冲池中主要缓存的是磁盘中的数据页,由于数据页中没有一个字段用来表示内存中下一页的地址,InnoDB定义了"控制块"的数据结构,控制块中有一个指向数据页内存地址的指针,实现"控制块"与数据页的一一对应,并且把每个控制块连接成一个双向链表,用一个单独的头结点记录链表的第一个和最后一个结点,这样通过遍历控制块链表就可以遍历内存中的数据页.

3.2.3 衍生问题1: 内存中的数据页与磁盘上的数据页是什么关系

磁盘上的数据页加载到内存中后,在缓存池中都有一个内存页与他对应,只不过内存中管理的是控制块组成的链表,控制块有一个指针指向了内存中的真实的数据页.

3.2.4 衍生问题4: Buffer Pool的大小可以设置吗?

  • 可以通过系统变量innodb_buffer_pool_size进行设置,设置的时候以字节为单位: 默认值为134217728字节,即128MB.
  • 这里需要注意的是,InnoDB为"控制块"分配额外的内存空间,也就是控制块不会占用BufferPool的内存空间,所以实际分配的内存总空间比指定的缓冲池大小大10%左右.
  • 缓冲池设置的值越大,在多次访问相同表数据时,磁盘的IO就会越少,因为数据都已经缓存在内存中,所以效率也就越高,但是服务器启动时初始化的时间会比较长.

3.2.5 衍生问题3: chunk的作用是什么?

  • chunk是在服务器运行状态下动态调用缓冲池进行大小时操作的块大小,为了避免在调整大小操作期间复制所有缓冲池中的数据页,调整操作以"块"为基本单位执行.
    在这里插入图片描述

  • 比如在服务器运行时想要调整缓冲池的大小可以通过以下的sql语句:

#把缓冲池大小设置为1GB
mysql> SET GLOBAL innodb_buffer_pool_size=1073741824;
  • 注意: 启动调整大小操作的时候,在所有活动事务完成后操作才会开始,一旦调整大小操作开始,新的事务必须等到调整大小操作完成之后才可以访问缓冲池.

3.2.6 控制块与Page是如何初始化的?

  • 前面介绍了Chunk中管理的是具体的数据页,当缓冲池初始化完成时会把每个数据页所占用的内存空间和对应的控制块分配好,只不过是没有从磁盘加载数据时,内存中的数据页是空的而已.
  • 当缓冲池初始化的过程中,会为Chunk分配内存空间,此时"控制块"会从Chunk的内存空间中从左向右进行初始化,数据页所占用的内存会从Chunk的内存空间从右向左进行初始化,当所剩的内存空间不够一组"控制块"+数据页所占的空间时,就会产生内存碎片,如果刚好够用则不会出现内存碎片空间.
    在这里插入图片描述
  • 内存初始化完成之后,建立控制块与内存中缓冲池数据页之间的关系,从左开始第一个控制块直系那个第一个缓冲数据页的内存地址.
    在这里插入图片描述
  • 当前从磁盘中加载数据页的时候,就可以把数据缓存在内存中的空闲区域中.

3.2.7 可以通过缓冲池来提升性能吗?

当然可以,通过配置一下关于缓冲池的系统变量来提高性能,其中包括:

  • 配置缓冲池的大小
  • 配置多个缓冲池实例
  • 防止缓冲池扫描
  • 配置缓冲池预读取
  • 配置缓冲池刷新策略
  • 保存和恢复缓冲池状态
  • 从核心文件中排除缓冲页
  • 关于这些配置操作中,我们在后面的专题中介绍.

3.3 缓冲池中的页是如何进行管理的?

  • 当缓冲池初始化完成之后,缓冲池中的数据页只是被分配了内存空间,并没有真实的数据,当用户进行数据查- 询时真实的数据从磁盘加载到内存中并分配一个内存中的数据页,这时内存中的数据页的状态从空间变成了有实际的数据,当用户修改数据时,并不是直接修改磁盘中的数据页,而是修改内存中数据页,这时内存中数据页的状态从有实际数据变成了被修改.
  • 在缓冲池中采用三个链表维护内存页,这三个链表也对应着内存中页的三种状态,分别是:
    • Free未使用的页,也可以称作空闲页.
    • Clean已经使用但是未修改的页,也可以称作干净页.
    • Dirty已修改的也,也可以称作脏页.
  • 对应的三个链表分别是Free List,LRU List和Flush List
    • Free List: 只管理Free页
    • LRU List: 管理Clean页和Dirty页
    • Flush List: 只管理Dirty页.
      在这里插入图片描述
  • Free List: 管理着空闲的也就是没有被使用过的内存页,当执行查询操作时,如果对应的也已经在buffer pool中则直接返回数据,如果没有且Free List不为空,则从磁盘中查询对应的数据并保存到Free List的某一页,然后把这个页从Free List中移除并放入LRU List中.
  • LRU List: 管理所有从磁盘中读取的数据页,包括未被修改的和已经被修改的数据页,并根据LRU算法对链表中的页节点进行维护与淘汰,当数据库刚启动时LRU List是空的,这时候从内存中申请到的页都在存放在Free List中,当数据中磁盘读取到缓冲池时,首先从Free List中查找是否有空闲的页,如果有则把该页从Free List中删除并加入到LRU List ,如果没有,则根据LRU算法淘汰LRU List末尾的页,并将该内存空间分配给新数据页.
  • Flush List: 当LRU List中的页被修改后会被标识为脏页,并把脏页加入到FlushList中,在这种情况之下,数据库通过刷盘机制把Flush List中的脏页刷会磁盘,Flush List是一个专门用来管理脏页的列表.脏页即存在于LRU List中每页存在于Flush List中,LRU List用来管理缓冲池中页的可用性,Flush List用来管理要被刷会磁盘的页,二者互不影响,Flush List中的脏页在执行了刷盘操作后会将空间还给Free List.
  • 总结: 每个缓冲池都采用三个链表维护内存页,这三个链表对应着内存中的三种状态,分别是:
    • Free未使用的页,也可以叫做空闲页.
    • Clean已使用的页,也可以称作干净页.
    • Dirty已修改的页,也可以称作脏页.

3.3.1 衍生问题1: 内存中有这么多数据页如何快速找到目标页

  • 首先第一种办法就是通过遍历,这种做法显然不能满足性能要求.
  • InnoDB采用的PageHash的方式,也就是每当把磁盘中数据页加载到内存时,用数据页的表空间Id和页号座位Key,当前页在内存中的地址作为Value保存起来,每次查询时就可以通过key快速定位到目标页,如果内存中没有目标页,则从磁盘中获取.

3.3.2 衍生问题2: 缓冲池中的数据放不下怎么办?

InnoDB根据自身的使用场景,使用淘汰策略来淘汰相应的数据页,从而释放出内存空间,以便更新的数据页加载到内存中.

3.4 缓冲池采用哪种淘汰策略?是如何实现的?

  • 缓冲池淘汰策略采用变形的最近最少使用的(LRU)算法(在原来的LRU算法的基础上做了修改),以下出现的LRU算法指的是LRU变形算法.
  • 缓冲池使用LRU算法管理链表,当有新页面添加到缓冲池时,最近最少使用的页将被淘汰,并将新的页添加到列表的中间,这种中点插入策略将列表视为两个子列表:
    • 链表头部,是存放最近访问的**新页(年轻页)**子列表.
    • 链表尾部,是存放最近较少访问的旧页的子列表.
      在这里插入图片描述
  • 经常使用的页保存在新子列表中,较少使用的页保存在旧子列表中,随着时间的推移,旧子列表中的页将会被淘汰.
  • 总结: 缓冲池淘汰策略采用变形的最近最少使用(LRU)算法.

3.4.1 为什么要把页插入到中间而不是直接插入到新子列表的头部?

  • 因为InnoDB在读取页时,可能会发生"预读",预读的意思是InnoDB根据当前访问的记录自动推断后面可能会访问哪个页,并把他们加载到内存中,从而提高查询的效率,预读的页以并不一定会被真正的读取,从中间插入可以使起尽快被淘汰.

4. 变更缓冲区

变更缓冲区在内存中的位置
在这里插入图片描述

4.1 变更缓冲区的作用?

  • 变更缓冲区占用BufferPool的一部分空间,具体如图所示:
    在这里插入图片描述
  • 变更缓冲区用来对缓存二级索引数据的修改,则是一个数据结构,当使用Insert,update,delete语句修改二级索引对应的数据的时候,如果对应的数据页在缓冲池中则直接更新,如果不在缓冲池中,那么就把修改操作缓存到变更缓冲区,这样就不需要立即从磁盘中读取对应的数据页了,之后的读操作将对应的数据页从磁盘加载到缓冲池中时,变更缓冲区中缓存的修改操作再批量合并到缓冲池,从而达到减少磁盘IO的目的.
    在这里插入图片描述
  • 总结: 变更缓冲区用来缓存对二级索引数据的修改,当数据页没有被回载到内存中时先把修改缓存起来,等到其他查询操作发生的时候数据页被加载到内存后,直接修改内存中的数据页,从而达到减少磁盘IO的目的.

4.1.1 衍生问题: 为什么是二级索引?

  • 关于数据库的索引,我们知道索引分为聚集索引和二级索引.
  • 由于聚集索引具有唯一性,我们来分析一下聚集索引为什么不能被放入变更缓存,假设表中有一个主键,现在有两条insert语句,都在插入数据id的值相同,那么在变更缓冲区中就存在两个修改的操作,如果以后要合并到缓冲池,这时就会出现重复的主键值,所以聚集索引的修改不能被加入到变更缓冲区.
  • 与聚集索引不同,二级索引通常不是唯一的,并且向二级索引中插入数据时由于数据列不同,所以位置相对随机,同样对于删除和更新操作可能会影响不相邻的二级索引页,如果每次都从磁盘中读取数据就会发生大量的随机IO,以变更缓冲区的方式先将修改缓存起来,当真正去读取数据时再把修改合并到缓冲池中可以提升效率.

4.2 变更缓冲区的主要配置项有哪些?

  • 主要的配置项有缓冲类型和更改缓冲区的最大大小.
  1. 缓冲类型
    在修改二级索引数据变更缓冲区可以减少磁盘IO从而提高效率,但是变更缓冲区占用了缓冲池中的一部分空间,从而减少了可用于缓存数据页的内存,如果业务场景读多写少,或是表中的二级索引相对较少,那么可以考虑禁用更改缓冲区从而提高缓冲池的空间.
    可以通过选项文件或set global语句对系统变量innodb_change_buffering进行设置,来控制变更缓冲区对于插入,删除操作(索引记录被标记为删除)和清除操作(当索引记录被物理删除时)的开启或禁用:

删除操作: 索引记录被标记为删除
清除操作: 索引记录被物理删除
更新操作: 是插入和删除操作的组合

  • all: 默认值,缓存插入,删除标记操作和清除.
  • none: 不缓存任何数据
  • inserts: 只缓存插入操作
  • deletes: 只缓存删除标记操作
  • changes: 缓存插入和删除标记操作.
  • purges: 缓存发生在后台的物理删除操作.
  1. 更改缓冲区的最大大小
  • 通过innodb_change_buffer_max_size系统变量可以设置更改缓冲区的最大大小,默认为25,最大为50,表示更改缓冲区占用缓冲池内存总大小的百分比.
  • 在有大量插入,更新和删除的业务场景中,可以考虑增加innodb_change_buffer_max_size的值,大部分是读多写少,比如用户报表的金泰数据场景中考虑减少innodb_change_buffer_max_size的值.
  • 需要注意的是,如果更改缓冲区占用了缓冲池太多的内存空间,会导致缓冲池中的数据页更快的淘汰.

5. 自适应哈希索引

  • 自适应哈希索引在内存中的位置
    在这里插入图片描述

5.1 自适应哈希索引的作用

  • 自适应哈希索引可以使用InnoDB存储引擎在不牺牲事务特新和可靠性以及缓冲池空间足够的前提之下提升效率,使用起来更像是内存数据库,哈希索引根据访问的索引也自动构建.
  • 根据InnoDB内部的监控机制,如果监控到某些查询通过建立哈希索引可以提高性能,则自动对这个也创建一个哈希索引,这个过程称为自适应,所以叫做自适应哈希索引.
  • 如果表完全放在内存中,则哈希索引可以通过直接查找任何元素来加快查询速度.
  • 总结: 自适应哈希索引主要的作用是提升查询速率.

5.1.1 衍生问题1: 为什么要建立自适应哈希索引?

  • InnoDB存储引擎的数据存储于B+树中,B+树通常只有3-5层,但是从根节点到叶子结点的寻路涉及到多层页面内记录的比较,即使所有路径上的页面都在内存中,也非常消耗CPU资源.
  • InnoDB对寻路的开销进行了优化,比如寻路结束之后将cursor缓存起来方便下次使用,尽可能的避免寻路开销.
  • 本质上是通过缩短寻路路径从而提升MySQL查询性能的一种方式,在内存级别进一步提升查询效率.

5.1.2 衍生问题2: 自适应哈希索引保存在哪里?

自适应哈希索引会占用缓冲池一部分的内存区域,在缓冲池初始化后被初始化,为了避免自适应哈希索引的锁竞争压力,自适应哈希索引支持分区,可以使用innodb_adaptive_hash_index_parts参数配置分区个数,默认是8.

6. 日志缓冲区

日志缓冲区在内存中的位置
在这里插入图片描述

6.1 日志缓冲区的作用

  • 日志缓冲区是服务器启动时相操作系统申请的一片连续的内存区域,存储即将要写入的磁盘日志文件的数据.
  • 在对数据库进行CRUD操作时,InnoDB会记录对应操作的日志,比如为保证数据完整性实现数据库崩溃恢复的Redo Log,这些日志会首先写入Log Buffer中,从而解决同步写磁盘导致的性能问题,然后根据不同的落盘策略最终写入磁盘.

6.1.1 衍生问题1: 日志不通过Log Buffer直接写入磁盘不行吗?

如果日志不通过LogBuffer直接写入磁盘,那么每次进行CRUD操作都会进行一次磁盘IO,这样会严重影响效率,所以把日志统一写入内存中的LogBuffer,根据刷盘策略统一进行落盘操作,可以实现一次磁盘IO写入多条日志,从而提升效率.

6.1.2 衍生问题2: Log Buffer与日志文件是如何配合工作的?

关于Log Buffer与日志文件之间的交互过程,RedoLog的结构,RedoLog的写入时机,我们在后面介绍.

相关文章:

  • 2025能源网络安全大赛CTF --- Crypto wp
  • maptalks在地图中进行矩形绘制,并把绘制区域截图下载
  • uniapp自定义底部导航栏,解决下拉时候顶部空白的问题
  • 决策卫生问题:考公考编考研能补救高考选取职业的错误吗
  • JavaScript 对象复制:浅拷贝与深拷贝
  • DBA工作常见问题整理
  • Vue 3 reactive 和 ref 区别及 失去响应性问题
  • Chromium 134 编译指南 macOS篇:获取源代码(四)
  • LeetCode hot 100—括号生成
  • Hyperf (Swoole)的多进程 + 单线程协程、Gin (Go)Go的单进程 + 多 goroutine 解说
  • 深入剖析 ORM:原理、优缺点、场景及多语言框架示例
  • 消除异步的传染性(代数效应)
  • ARINC818-1协议
  • 网易游戏 x Apache Doris:湖仓一体架构演进之路
  • 鸿蒙系统开发中路由使用详解
  • 【并行分布计算】Hadoop伪分布搭建
  • 【并行分布计算】Hadoop单机分布搭建
  • ubuntu docker 创建镜像 报错 dial tcp xxxx read udp xxxx i/o timeout 还有 Forbidden
  • 秘密任务 2.0:如何利用 WebSockets + DTOs 设计实时操作
  • Redis面试——常用命令
  • 海南医科大披露校内竞聘上岗结果:32名干部离开领导岗位,8人系落选
  • 安徽凤阳县明中都鼓楼楼宇顶部瓦片部分脱落,无人员伤亡
  • 搜狐一季度营收1.36亿美元,净亏损同比收窄超两成
  • 以色列在加沙发起新一轮强攻,同步与哈马斯展开“无条件谈判”
  • 世界高血压日|专家:高血压患者控制血压同时应注重心率管理
  • 上海市第二十届青少年科技节启动:为期半年,推出百余项活动