📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨
文章目录
11111111
11111111
11111111
11111111
**** 11111111
🏳️🌈一、InnoDB存储引擎中内存结构的主要组成部分有哪些?
InnoDB存储引擎中内存结构主要分为:
Buffer Pool
缓冲池Change Buffer
变更缓冲区adaptive_hash_index
自适应哈希索引Log Buffer
日志缓冲区
🏳️🌈二、为什么需要内存结构?
- 这个问题在
InnoDB
架构章节已经做了解释,再来回顾一下: - 从
MySQL
实现的角度来思考这个问题,数据库的作用就是保存数据,用户的真实数据最终都会保存在磁盘上,在查询数据的过程中,如果每次都从磁盘上读取会严重影响效率 ,为了提高数据的访问效率,InnoDB 会把查询到的数据缓存到内存中,当再次查询时,如果目标数据已经存在于内存中,就可以从内存中直接读取,从而大幅提升效率。 - 也就是说
磁盘结构
中的文件是用来保存数据实现数据持久化的,内存结构
是用来缓存数据提升效率的。
🏳️🌈三、缓冲池 - Buffer Pool
3.1 缓冲池的作用
- 缓冲池主要用来缓存被访问的
InnoDB表
和索引数据页
,是主内存中的一片区域,允许直接从内存访问频繁使用的数据从而提高效率。在专用数据库服务器上,通常会将多达80%的物理内存分配给缓冲池。 - 其次缓冲池不仅缓存了磁盘的数据页,也存储了锁信息、Change Buffer信息、Adaptive hashindex、Double write buffer等信息。如上图所示
3.2 缓冲池是如何组织数据的?
- 缓冲池组织数据的方式也可以说是缓冲池用到的数据结构,在这之前回顾一下
InnoDB
表空间的存储结构。 - 每个
InnoDB
表空间在磁盘上对应一个.ibd
文件,其中包含了叶子节点段和非叶子节点段等逻辑段,段中包含了区组,区组中管理着区,区别包含数据页,数据页中包含数据行,每分别对着不同的数据结构目的就是便于数据的管理与高效访问
3.2.1 缓冲池的结构是怎样的?
- 从缓冲池的概念了解到它是主内存中的一片区域,在专用服务器上会将多达80%的物理内存分配给缓冲池,在这么大的内存空间中如何保证效率就是要解决的问题
- 缓冲池也采用与表空间类似的方式对数据进行组织,如下图所示:
- 缓冲池中包含至少一个
Instances
实例,Instances
是真正的缓冲池的实例对象,内存操作都是在Instances
中进行的; - 每个 Instances 中包含至少一个
Chunk
块,Chunk 是在服务器运行状态下动态调缓冲池进行大小时操作的块大小; - 每个块中包含和管理若干个从磁盘加载到内存的 Page 数据页
- 缓冲池中包含至少一个
- 可以看出缓冲池通过定义不同的数据结构,但最终管理的是每个数据页,这些数据页是从磁盘中加载到内存的,也就是说磁盘中的数据页加载到内存中之后,对应的就是内存中的数据页,并且页与页之间用链表连接。
- 那么这时就有一个问题,我们知道磁盘中的数据页大小默认是16KB,并且通过头信息中的
next_record
记录下一行地址偏移量,在页的结构定义中并没有一个字段用来表示内存中下一页的地址,那么在内存中如何为每个页建立连接呢?
3.2.2 缓冲池中页与页之间是如何建立连接的?
由于数据页中没有一个字段用来表示内存中下一页的地址,为了每个数据页在内存中实现链表连接,InnoDB
定义了一个叫"控制块"的数据结构,"控制块"中有三个重要的信息分别是:
- 指向数据页的内存地址
- 前一个控制块的内存地址
- 后一下控制块的内存地址
之后再用一个双向链表管理每个控制块,如下图所示:
为了确定控制块链表的超始位置,专门定义了一个头节点,头节点中包含了三个主要的信息,如图中所示:
- 第一个控制块的内存地址
- 最后一个控制块的内存地址
- 链表中控制块的数量
通过遍历控制块链表就可以遍历内存中的数据页
缓冲池中主要缓存的是磁盘中的数据页,由于数据页中没有一个字段用来表示内存中下一页的地址,InnoDB定义了"控制块"的数据结构
控制块中有一个指向数据页内存地址的指针,实现 “控制块” 与 数据页 的一一对应,并且把每个控制块连接成一个双向链表,用一个单独的头节点记录链表的第一个和最后一个节点,这样通过遍历控制块链表就可以遍历内存中的数据页。
3.2.3 内存中的数据页与磁盘上的数据页是什么关系?
磁盘上的数据页加载到内存中后,在缓冲池中都有一个内存页与它对应,只不过内存中管理的是控制块组成的链表,控制块有一个指针指向了内存中真实的数据页
3.2.4 Buffer Pool 的大小可以设置吗?
- 可以通过系统变量
innodb_buffer_pool_size
进行设置,设置时以字节
为单位。默认值为134217728 字节,即 128MB;最大值取决于CPU架构和操作系统,在32位系统上最大值为4294967295(232-1),在64位系统上最大值为 18446744073709551615(264 -1) - 这里需要注意的是,
InnoD8
为"控制块"分配额外的内存空间,也就是说"控制块"并不会占用Buffer Pool的内存空间,所以实际分配的内存总空间比指定的缓冲池大小大 10%左右。 - 缓冲池设置的值越大,在多次访问相同表数据时,
磁盘I/O
就会越少,因为数据都已经缓存在内存中,所以效率也就越高,但是服务器启动时初始化时间会比较长。 - 查看缓冲池大小可以使用下面的 SQL语句
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name | Value |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.04 sec)
3.2.5 Buffer Pool 中 Instances 的数量如何确定?
- 通过系统变量
innodb_buffer_pool_instances
可以设置缓冲池实例的个数,默认是 1,最大值 64; - 当缓冲池的大小
小于 1GB
时,无论指定 innodb_buffer_pool_instances 数是多少都会自动调整为1
; - 当缓冲池的大小
大于 1GB
时,innodb_buffer_pool_instances 默认值为8
,也可以指定大于1的值来设置 Instances 的数量,多个Instances 可以提升服务器的并发性; - 为了获得最佳的效率,通过指定
innodb_buffer_pool_instance
s 和innodb_buffer_pool_size
为每个缓冲池实例设置至少为 1GB 的空间; - 查看
Instances
的数量可以使用下面的SQL语句
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1 |
+------------------------------+-------+
1 row in set (0.01 sec)
3.2.6 Chunk的作用是什么?
Chunk
是在服务器运行状态下 动态调缓冲池进行大小时操作的块大小
,为避免在调整大小操作期间复制所有缓冲池中的数据页,调整操作以 “块
” 为基本单位执行;
比如在服务器运行时想要 调整缓冲池的大小 可以通过以下SQL语句:
mysql> SET GLOBAL innodb_buffer_pool_size=1073741824;
Query OK, 0 rows affected (0.01 sec)
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+------------+
| Variable_name | Value |
+-------------------------+------------+
| innodb_buffer_pool_size | 1073741824 |
+-------------------------+------------+
1 row in set (0.00 sec)
注意: 启动调整大小操作时,在所有活动事务完成后操作才会开始。一旦调整大小操作开始,新的事务和操作必须等到调整大小操作完成才可以访问缓冲池。
3.2.7 lnstances中Chunk的数量如何确定?
Chunk
大小可以通过系统变量 innodb_buffer_pool_chunk_size
进行设
- 默认为
134217728字节
即128MB
- 在设置大小时可以以
1048576字节
即1MB
为单位增加或减少; - 块中包含的数据页数取决于
innodb_page_size
约4~64KB
更改 innodb_buffer_pool_chunk_size
的值时注意以下条件:
- 如果
innodb_buffer_pool_chunk_size
*innodb_buffer_pool_instances
大于当前缓冲池大小,innodb_buffer_pool_chunk_size
将被截断为innodb buffer_pool size
/innodb_buffer_pool instances
. - 缓冲池大小必须始终等于或倍数于
innodb_buffer_pool_chunk_sizeinnodb_buffer_pool_instances
。如果修改了innodb_buffer_pool_chunk_size
的值,导致不符合这个规则,那么在缓冲池初始化时innodb_buffer_pool_size
会自动四舍五入为等于或者倍数于innodb_buffer_pool_chunk_size
*innodb_buffer_pool_instances
的值。
innodb_buffer_pool_chunk_size
的设置可能会影响到缓冲池大小
innodb_buffer_pool_size
的值,
所以要调整innodb_buffer_pool_chunk_size
的值之前一定要计算好
3.2.8 控制块与Page是如何初始化的?
- 前面介绍了
chunk
中管理的是具体的数据页,当缓冲池初始化完成时会把每个数据页所占用的内存空间和对应的控制块分配好,只不是没有从磁盘加载数据时,内存中的数据页是空的而已。 - 当缓冲池初始化的过程中,会为
chunk
分配置内存空间,此时"控制块
"会从chunk
的内存空间从左向右
进行初始化,数据页
所占的内存会从chun
k 的内存空间从右向左
进行初始化,当所剩的内存空间不够一组 “控制块” + 数据页 所占的空间时,就会产生碎片空间,如果适好够用则不会出现碎片空间,如下图所示:
内存初始化完成之后,建立控制块与内存中缓冲数据页之间的关系,从左开始第一个控制块指向第一个缓冲数据页的内存地址
当前从磁盘中加载数据页时,就可以在把数据缓存在内存中的空闭数据页中
3.2.9 可以通过缓冲池配置来提升性能吗?
当然可以,通过配置以下关于缓冲池的系统变量来提高性能,其中包括
- 配置缓冲池大小
- 配置多个缓冲池实例
- 防止缓冲池扫描
- 配置缓冲池预取(预读)
- 配置缓冲池刷新策略
- 保存和恢复缓冲池状态
- 从核心文件中排除缓冲池页
- 这些变量可以通过以下语句查看
mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool%';
+-------------------------------------+----------------+
| Variable_name | Value |
+-------------------------------------+----------------+
| innodb_buffer_pool_chunk_size | 134217728 |
| innodb_buffer_pool_dump_at_shutdown | ON |
| innodb_buffer_pool_dump_now | OFF |
| innodb_buffer_pool_dump_pct | 25 |
| innodb_buffer_pool_filename | ib_buffer_pool |
| innodb_buffer_pool_in_core_file | ON |
| innodb_buffer_pool_instances | 1 |
| innodb_buffer_pool_load_abort | OFF |
| innodb_buffer_pool_load_at_startup | ON |
| innodb_buffer_pool_load_now | OFF |
| innodb_buffer_pool_size | 1073741824 |
+-------------------------------------+----------------+
11 rows in set (0.00 sec)
3.3 缓冲池中的页是如何进行管理的?
每个缓冲池都采用三个链表维护内存页,这三个链表也对应着内存中页的三种状态,分别是:
- Free 未使用的页,也可以称做空闲页;
- Clean 已使用但未修改的页,也可以称做干净页;
- Dirty 已修改的页,也可以称做脏页。
- 当缓冲池初始化完成后,缓冲池中的数据页只是被分配了内存空间,并没有真实的数据,当用户进行数据查询时真实的数据从磁盘加载到内存中并分配一个内存中的数据页,这时内存中数据页的状态从空间变成了有实际的数据;
- 当用户修改数据时,并不是直接修改磁盘中的数据页,而是修改内存中数据页中的数据页,这时内存中数据页的状态
从有实际数据变成了被修改
。 - 在缓冲池中采用三个链表维护内存页,这三个链表也对应着内存中页的三种状态,分别是:
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 中的页被修改后会被标识为脏页(Dirty page),并把脏页加入到Flush List 中,在这种情况下,数据库会通过刷盘机制把 Flush List 中的脏页刷回磁盘; Flush List 是一个专门用来管理脏页的列表。脏页既存在于 LRU List 中,也存在于Flush List 中,LRU List 用来管理缓冲池中页的可用性, Flush List 用来管理要被刷回磁盘的页,二者互不影响。 Flush List 中的脏页在执行了刷盘操作后会将空间还给 FreeList 。
3.3.1 内存中有这么多数据页如何快速找到目标页?
- 首先第一种办法是通过遍历,这种做法显示不能满足性能要求
- InnoDB采用的是
Page Hash
的方式,也就是每当把磁盘中数据页加载到内存时,用数据页的表空间ld和页号做为Key,当前页在内存中的地址做为Value保存起来,每次查询时就可以通过Key快速定位到目标页,如果内存中没有目标页,则从磁盘中获取。
3.3.2 缓冲池中的数据放不下了怎么办?
InnoDB根据根据自身的实际场景,使用 淘汰策略
来淘汰相应的数据页,从而释放出内存空间,以便新的数据页加载到内存中。
3.4 缓冲池采用哪种淘汰策略? 是如何实现的?
缓冲池淘汰策略采用变形的最近 最少使用(LRU)算法
(在原来LRU算法的基础做了修改),以下出现的LRU算法指的是LRU变形算法。
最近最少使用的页将被淘汰,并将新缓冲池使用 LRU算法管理链表,当有新页面添加到缓冲池时,页添加到列表的中间,这种中点插入策略将列表视为两个子列表:
- 链表头部,是存放最近访问的新页(年轻页)子列表;
- 链表尾部,是存放最近较少访问的旧页子列表。
经常使用的页保存在 新子列表 中,较少使用的页保存在 旧子列表 中,随着时间的推移,旧子列表中的页将会逐渐被淘汰。默认情况下,算法的执行过程如下:
- 缓冲池总容量的
5/8
用于新子列表,3/8
用于旧子列表; - 列表的中间插入点是
新子列表的尾部
与旧子列表头部
的交界; - 当一个页被读入缓冲池时,首先插入到中点做为旧子列表的头节点;
- 当访问的页在旧子列表中时,把被访问的页移动到新子列表的头部,使其成为"新"页;
- 数据库运行的过程中,缓冲池中被访问页面的位置不断更新,未访问的页面向列表的尾部移动,从而逐渐"变老"最终超出缓冲池容量的页从旧子列表的尾部被淘汰。
3.4.1 为什么要把页插入到中间而不是直接插入到新子列表的头部?
因为InnoDB在读取页时,可能会发生"预读",预读的意思是InnoDB根据当前访问的记录自动推断后面可能会访问哪个页,并把他们提前加载到内存中,从而提高以后查询的效率,预读的页以并不一定会被真正的读取,从中间点插入可以使其尽快被淘汰。
mysql> SHOW ENGINE INNODB STATUS\G
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
... # 省略
----------------------
BUFFER POOL AND MEMORY # 缓冲池和内存
----------------------
Total large memory allocated 2198863872 # 为缓冲池分配的总内存,以字节为单位
Dictionary memory allocated 776332 # 为InnoDB数据字典分配的总内存,以字节为单
位
Buffer pool size 131072 # 分配给缓冲池的总⻚⼤⼩
Free buffers 124908 # 缓冲池空闲列表的总⻚⼤⼩
Database pages 5720 # 缓冲池LRU列表的总分⻚⼤⼩
Old database pages 2071 # 缓冲池旧⼦列表的总⻚⼤⼩
Modified db pages 910 # 缓冲池中当前修改的⻚数(脏⻚)
Pending reads 0 # 等待读⼊缓冲池的⻚数
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 4, not young 0
0.10 youngs/s, 0.00 non-youngs/s
Pages read 197, created 5523, written 5060
0.00 reads/s, 190.89 creates/s, 244.94 writes/s
Buffer pool hit rate 1000 / 1000, young-making rate 0 / 1000 not
0 / 1000
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read
ahead 0.00/s
LRU len: 5720, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
... # 省略
InnoDB缓冲池指标如下,以供参考:
小结
- 缓冲池是用来缓存各种数据,最主要的就是缓存从磁盘中加载的数据页,从而提升效率
- 缓冲池为了方便数据组织定义了不同的数据结构,包括Instances 实例,Chunk块,其中 Buffer Pool中包含至少-个 Instances,Instances 包含至少一个Chunk 块,Chunk 管理若干个从磁盘加载到内存的 Page 数据页
- 缓冲池能过三个链表管理内存中的数据页,分别是Free List、LRU List 和 Flush List
- 缓冲池淘汰策略采用变形的最近最少使用(LRU)算法
👥总结
本篇博文对 【MySQL】从 InnoDB 内存结构的组成及原因 到 缓冲池详解 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~