【MYSQL】MYSQL 的学习教程(三)之 InnoDB 结构

对于 InnoDB 存储引擎来说,数据是存储在磁盘上,而存储引擎想要操作数据,必须先将磁盘的数据加载到内存中才能操作。当数据从磁盘中取出后,缓存内存中,下次查询同样的数据的时候,直接从内存中读取,这样大大提高了查询性能。

1、InnoDB 结构图

InnoDB 结构图如下:

在这里插入图片描述

MySQL InnoDB Architecture的体系结构图在这里:MySQL5.7 版本官方 InnoDB 结构图

①:In-Memory Structures【内存结构】主要是针对的是数据及其操作,主要分为:

  • Buffer Pool:缓冲池。在访问数据、索引时缓存的主内存区域。缓冲池被划分为多个 Page 页,将数据存放在了Page页中,在缓冲池Page页是通过链表形式来存放的,使用频率最少的数据将从链表尾部剔除
  • Change Buffer:写缓冲区。正常情况下修改数据是先修改的缓冲池中 Page 页的数据,但是缓冲池肯定不是所有的数据,而修改数据没有对应的 Page 页数据的时候并不会直接把数据加载到缓冲池中去,而是放在了写缓冲区中记录,等到数据被读取的时候再把数据合并到缓冲池中
  • Adaptive Hash Index自适应 Hash 索引。InnoDB 存储引擎会根据 Page 页的访问频率和模式建立对应的 Hash 索引,这个索引是根据查询情况自动建立的,称为自适应 Hash 索引
  • Log Buffer:日志缓冲区。主要用来保存写入磁盘的(Redo/Undo)日志文件,日志缓冲区会定期刷新到磁盘 log 文件中,这样不用每次日志都进行磁盘 IO 操作,提高效率

②:On-Disk Structures【磁盘结构】主要针对的是表和表空间,主要分为以下结构:

  • Tablespaces:表空间。用来存储表结构和数据的。MySQL 的表空间

    • System Tablespace:系统表空间/共享表空间

    • File-Per-Table Tablespaces:独立表空间。InnoDB 可以选择使用系统表空间还是独立表空间存储表。如果选择前者,则所有 InnoDB 表都保存在 ibdata1 文件中;选择后者,则一个表占据一个表文件——表名.ibd,拥有自己独立的表文件

    • Undo Tablespaces:undo 表空间

    • General Tablespaces:通用表空间

    • Temporary Tablespaces:临时表空间

  • InnoDB Data Dictionary:数据字典,InnoDB 数据字典由内部系统表组成,这些表包含用于查找表、索引和表字段等对象的元数据

  • Doublewrite Buffer:双写缓冲区,我们知道数据修改先修改的 Page 页后又刷到磁盘的,在刷到磁盘前这些数据会先存放在双写缓存区中,双写缓存区是用来保障数据写入磁盘时候出现问题的备份

  • Redo Logs:重做日志,记录了所有缓冲池修改的数据,修改数据的时候先写日志,后修改的缓冲区,假设修改写入操作的时候数据库崩溃了或停电了,等下次启动通过重做日志来保持数据的正确性

2、Buffer Pool 缓冲池

Buffer Pool 是 MySQL 服务在启动的时候向操作系统申请的一片连续地址的内存空间,其本质就是一片内存,默认大小是 128M,可以在启动服务的时候,通过 innodb_buffer_pool 这个参数设置buffer pool的大小,单位是字节(B),最小值是 5MB

Buffer Pool 内存中的信息:
在这里插入图片描述

因为 Buffer Pool 被划分为某干个数据页/缓存页,其数据页大小和表空间使用的页大小一致,为了更好的管理Buffer Pool 中的缓冲页,InnoDB为每个缓冲页都创建了一个控制信息

控制信息主要包括该缓冲页的【表空间编号、页号、缓冲页在 Buffer Pool 中的地址、链表节点信息】

缓冲页和控制块是一 一对应的,其中,控制块在 Buffer Pool 前面,而缓冲页在 Buffer Pool 后面。如图:
在这里插入图片描述

什么是碎片?当剩余空间不够一对控制块和缓冲页的大小时,这样的空间称为碎片。

3、Buffer Pool 管理数据页

Buffer Pool 中的页有三种状态:

  • 空闲页:通过空闲页链表(Free List)管理。
  • 正常页:通过 LRU 链表(LRU List)管理。
  • 脏页:缓冲池中被修改过的页,与磁盘上的数据页不一致。通过 LRU 链表和脏页链表(Flush List)管理。

3.1、Free 链表

初始化完 Buffer Pool 时,所有的页都是空闲页,所有空闲的缓冲页对应的控制块信息作为一个节点放到 Free 链表中。如图:
在这里插入图片描述

注意:Free 链表是一个个控制块,而控制块的信息中有缓存页的地址信息

在有了 Free 链表之后,当需要加载磁盘中的页到 Buffer Pool 中时,就去 Free 链表中取一个空闲页所对应的控制块信息,根据控制块信息中的表空间号、页号找到 Buffer Pool 里对应的缓冲页,再将数据加载到该缓冲页中,随后删掉 Free 链表该控制块信息对应的节点。

如何在 Buffer Pool 中快速查找缓冲页(数据页)呢?

可以对缓冲页进行 Hash 处理:用表空间号、页号做为 Key,缓冲页的控制块是 value,维护一个 Hash 表。根据表空间号、页号做为 Key 去查找有没有对应的缓冲信息。如果没有,就需要去 Free 链表中取一个空闲的缓冲页控制块信息,随后将磁盘中的数据加载到该缓冲页位置;否则,就查找到了。

3.2、Flush 链表

修改了 Buffer Pool 中缓冲页的数据,那么该页和磁盘就不一致了,这样的页就称为【脏页】,它不是立马刷入到磁盘中,而是由后台线程将脏页写入到磁盘。

Flush 链表就是为了能知道哪些是脏页而设计的,它跟 Free 链表结构图相似,区别在于控制块指向的是脏页地址。

3.3、LRU 链表

因为缓冲空间大小是有限的,为了避免占用 Buffer Pool 的空间,我们希望很少访问【访问次数少】的数据在某个时机淘汰掉。MySQL 根据 LRU 算法设计了 LRU 链表来维护和淘汰缓冲页。

LRU 算法:如果用链表来实现,将最近命中(加载)的数据页移在头部,未使用的向后偏移,直至移除链表。这样的淘汰算法就叫做 LRU 算法,但是简单的 LRU 算法会带来两个问题:预读失效、Buffer Pool 污染

3.3.1、预读机制和预读失效

  • 预读机制:当数据页从磁盘加载到 Buffer Pool 中时,会把相邻的数据页也提前加载到 Buffer Pool 中,这样做的好处就是减少未来可能的磁盘 IO
  • 预读失效:当预读机制提前加载的数据页一直未被访问,这就是失效
    结合简单的 LRU 算法来看:可能预读页被加载到 LRU 链表头部,当 Buffer Pool 空间不够时,会把不经常访问的位于 LRU 链表的尾部数据页给淘汰清理掉,这样缓冲就失效了

3.3.2、改进的 LRU 算法

Buffer Pool 的 LRU 算法中 InnoDB 将 LRU 链表按照 5:3 的比例分成了 young 区域和 old 区域。链表头部的 5/8 是 young区(被高频访问数据),链表尾部的 3/8 区域是 old 区域。

在预读的时候或访问不存在的缓冲页时,先加入到 old 区域的头部,当页被真正访问的时候,才将页插入 young 区域的头部

如图:
在这里插入图片描述

  1. 当访问的缓冲页 P3 在 young 区域时,P3 在 young 区域会移动到链表头
  2. 现在 P8 被预读了,移动到 old 区域的 head 头部,而 P7 将会被淘汰掉
  3. 如果 P8 预读后立即被访问(热点数据),那么 P8 插入到 young 区域的头部,但是如果 P8 一直未被访问,也没有占用 young 区域位置,很快会被淘汰出去

4、多 Buffer 实例

我们已经默认情况下 innodb_buffer_pool_size 是128M, 此时的 innodb_buffer_pool_instances 的大小也就是实例是1个。因为 innodb_buffer_pool_size 小于1G 时,设置 innodb_buffer_pool_instances 是无效的,都会是 1。

当一个buffer pool在多线程访问的时候,各个链表都会加锁处理,这样一来,多线程访问时,性能就会降低。

可以通过 innodb_buffer_pool_instances 参数来设置实例的个数。每个 buffer pool 实例的大小计算公式:innodb_buffer_pool_size / innodb_buffer_pool_instances,每个实例都有其对应的链表管理,互不干扰

4.1、修改 Buffer Pool 大小

如何修改运行中 MySQL 的 Buffer Pool 的大小?

  • MySQL 5.7.5之前:是不允许在运行时调整 Buffer Pool 大小的,只能在服务器启动之前,通过innodb_buffer_pool_size 大小来调整
  • MySQL 5.7.5之后:是以 chunk 为单位来修改 Buffer Pool 的大小,比如 innodb_buffer_pool_chunk_size默认大小是128M,调整 Buffer Pool 大小就以 chunk 为单位来增加或减少 Buffer Pool 大小

一个 Buffer Pool 可能有多个 Buffer Pool 实例,而每个实例由多个 chunk 组成,一个 chunk 是一块连续的内存空间,一个 chunk 默认大小是 128M

如图:
在这里插入图片描述

5、Change Buffer 写缓冲区

在这里插入图片描述

InnoDB 从1.0.x 版本开始引入了Change Buffer,可将其视为 Insert Buffer 的升级。从这个版本开始,InnoDB存储引擎可以对 insert、update、delete 操作都进行缓冲,分别对应的是:Insert Buffer、Update Buffer、Delete Buffer。等到数据被读取的时候再把写缓存区的数据加载到缓冲池里。与之前的 Insert Buffer 一样,Change Buffer 适用的对象依然是唯一性辅助索引

当对一条记录进行 UPDATE 操作可能分为两个过程:

  • 将记录标记为已删除;
  • 真正将记录删除;

因此,Delete Buffer 对应的是 UPDATE 操作的第一个过程,即将记录标记为删除。Purge Buffer 对应 UPDATE操作的第二个过程,即将真正的记录删除。

从 InnoDB 1.2.x 版本开始,可以通过参数 innodb_change_buffer_max_size 来控制 Change Buffer 的最大使用内存数量【改参数的最大有效值为50】

Change Buffer 原理:当需要更新一个数据页时,如果数据页在内存中就直接更新。如果数据页不在内存中。在不影响数据一致性的前提下,InooDB 会将这些更新操作缓存在 change buffer 中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer 中与这个页有关的操作。通过这种方式就能保证这个数据逻辑的正确性。虽然叫作 change buffer,实际上它是可以持久化的数据。也就是说,change buffer 在内存中有拷贝,也会被写入到磁盘上(ibdata 文件)。将 change buffer 中的操作合并到原数据页,得到最新结果的过程称为 merge。以下情况会触发merge:

  • 访问这个数据页;
  • 后台 master 线程定期 merge;
  • 数据库缓冲池不够用时;
  • 数据库正常关闭时;
  • redo log 写满时

6、Adaptive Hash Index 自适应哈希索引

哈希是一种非常快的查找算法,在一般情况下,这种查找的算法复杂度为 O(1),即一般仅需要一次查找就可以定位到数据。而 B+ tree 的查找次数,取决为 B+ 树的高度,生产环境下,B+ 树的高度一般为 3~4 层,所以需要 3~4 次的查询。InnoDB 存储引擎会监控对表上各索引页的查询。如果观察到建立哈希索引可以带来速度提升,则建立哈希索引,故称之为自适应哈希索引(Adaptive Hash Index)。自适应哈希索引是通过缓冲池中的B+树构造而来,因此建立的速度快,而且不需要对整张表构建哈希索引。InnoDB存储引擎会根据访问的频率来自动的为某些热点的数据页建立索引

7、Log Buffer 日志缓冲区

日志缓冲区是存储要写入磁盘上的日志文件的数据的内存区域。日志缓冲区大小由 innodb_log_buffer_size 变量定义。默认大小为 16MB。日志缓冲区的内容定期刷新到磁盘。大的日志缓冲区允许运行大型事务,而不需要在事务提交之前将重做日志数据写入磁盘。因此,如果有更新、插入或删除许多行的事务,那么增加日志缓冲区的大小可以节省磁盘 I/O

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值