面试官问我 InnoDB 的物理存储结构!

前段时间去面试,面试官突然问我:聊聊 InnoDB 的物理存储结构吧!

树义突然又眼圈一黑,啥都想不起来了!

虽说之前有大致了解过 MySQL,但对 InnoDB 的物理结构,却真的没咋了解过!那么,今天就来聊聊 InnoDB 的物理存储结构吧!

相信很多人都知道逻辑结构和物理结构这两个概念,但是都很好奇它们的区别是什么?

简单地说:所谓物理存储结构,指的是 MySQL
的数据是怎么存储在物理介质上的,由哪些磁盘文件组成。所谓逻辑存储结构,指的是这些数据是如何有结构地组织起来的。

此文所描述的 InnoDB 物理存储结构,依据的是 MySQL 8.0 版本。

从 MySQL 官方文档中,我们可知 InnoDB 在物理层面上可划分为如下 7 个模块:

系统表空间(System Tablespace)双写缓存文件(Doublewrite Buffer Files)Undo 表空间(Undo Tablespaces)Redo Log 文件(Redo Log)独占表空间(File-Per-Table Tablespaces)通用表空间(General Tablespaces)临时表空间(Temporary Tablespaces)

而在这 7 个模块中,最为关键的是如下三个模块:系统表空间(System Tablespace)、独立表空间(File-Per-Table
Tablespaces)、日志文件组(Redo Log)。

面试官问我 InnoDB 的物理存储结构!插图亿华云

InnoDB 磁盘存储结构 MySQL 8.0

系统表空间(System Tablespace)

在开始之前,我们需要理解表空间这个概念。其实表空间,就是存储表的空间之意。它是 InnoDB
用于描述数据存储的术语,大致是存储表相关数据的地方之意。系统表空间,顾名思义就是 InnoDB 这个系统的默认表空间,也叫共享表空间,对应的是 MySQL
数据目录下的 ibdata1 文件。如果我们将 innodb_file_per_table 设置为 off,那么我们使用 CREATE TABLE
语句创建的表数据,都会存储在系统表空间中。

而如果我们将 innodb_file_per_table 设置为 on,则每个表将独立地产生一个表空间文件,以 ibd
结尾,数据、索引、表的内部数据字典信息都将保存在这个单独的表空间文件中。但是还有一些信息,例如 undo log 信息,还是会保存在系统表空间中。例如我们在
test 数据库中创建了一个名为 user 的表,那么就会在 MySQL 的数据文件夹的 test 文件夹下,有一个名为 user.ibd 文件。

我们注意到系统表空间还有一个名为 change buffer 的东西,它到底是啥东西呢?

change buffer,其实在之前版本是插入缓冲(insert buffer),后续版本变成了 change buffer(参考:内幕 2.4.1
章节)。这一块区域就是 Change Buffer。5.5 之前叫 Insert Buffer 插入缓冲,现在也能支持 delete 和 update,最后把
Change Buffer 记录到数据页的操作叫做 merge。

这里面有一句话很关键:除了主键聚合索引外,还产生了一个 name
列的辅助索引,对于该非聚集索引来说,叶子节点的插入不再有序,这时就需要离散访问非聚集索引页,插入性能变低。

这句话的意思是:聚集索引的插入是有序自增的,那么插入的时候直接跟在后面就可以了,不需要再做其他随机访问操作。而由于插入的非聚集索引列,并不是有序的,可能会插在中间,这时候就需要看看中间这个页是否加载到内存里。如果加载了,那还好,那就做
B 树操作,调整就行。不在内存里的话,那就麻烦了,需要读取多次 page 到内存。

为了提高效率,就弄了一个 insert buffer,简单地说,就是把多次 insert
或者修改操作,先缓存起来,然后等到差不多的时候,再一起去做,提高效率,避免多次 IO 读取。

插入缓冲,并不是缓存的一部分,而是物理页,对于非聚集索引的插入或更新操作,不是每一次直接插入索引页。而是先判断插入的非聚集索引页是否在缓冲池中。如果在,则直接插入,如果不再,则先放入一个插入缓冲区中。然后再以一定的频率执行插入缓冲和非聚集索引页子节点的合并操作。

当需要更新一个数据页时,如果数据页在内存中就直接更新,而如果这个数据页还没有在内存中的话,在不影响数据一致性的前提下,InnoDB 会将这些更新操作缓存在
change buffer 中,这样就不需要从磁盘中读入这个数据页了。在下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行 change buffer
中与这个页有关的操作。

双写缓存文件(Doublewrite Buffer Files)

如果说插入缓冲带给 InnoDB 存储引擎的是性能,那么双写带给 InnoDB
存储引擎的是数据的可靠性。当数据库宕机时,可能数据库正在写一个页面,而这个页只写了一部分(比如 16K 的页,只写了前 4K 的页),我们称之为写失效。在
InnoDB 存储引擎未使用 double write 之前,曾出现过因为部分写失效而导致数据丢失的情况。

InnoDB 存储引擎 double write 的体系架构如下图所示:

面试官问我 InnoDB 的物理存储结构!插图1亿华云

双写缓存(double write)示意图

double write 由两部分组成:一部分是内存中的 double write buffer,大小为 2MB。另一部分是物理磁盘上共享表空间中连续的
128 个页,即两个区,大小同样为 2MB。

当缓冲池的脏页刷新时,并不直接写磁盘,而是会他通过 memcpy 函数将脏页先拷贝到内存中的 doublewrite buffer,之后通过
doublewrite buffer 再分两次,每次写入 1MB 到共享表空间的物理磁盘上,然后马上调用 fsync
函数,同步磁盘,避免缓冲写带来的问题。在这个过程中,因为 doublewrite 页是连续的,因此这个过程是顺序写,开销不是很大。

在完成 doublewrite 页的写入之后,再将 doublewrite buffer 中的页写入各个表空间文件中,此时的写入则是离散的。我们可以通过
show global status like innodb_dblwr%\G 观察具体情况。

所以说双写,其实指的是刷脏页的时候,即会将脏页数据写入共享表空间的 2 个页中,也会写入具体的表空间中,共享表空间的数据起到备份作用。

如果操作系统在将页写入具体的表空间过程中崩溃了,在恢复过程中,InnoDB 存储引擎可以从共享表空间中的 doublewrite
中找到该页的一个副本,将其拷贝到表空间文件,再应用重做日志。

Undo 表空间(Undo Tablespaces)

undo Log 的数据默认在系统表空间 ibdata1 文件中,因为共享表空间不会自动收缩,也可以单独创建一个 undo 表空间。

一些疑问,关于 rollback segment 与 undo log?

很多时候,我们不太懂,为啥一些说 undo log 在系统表空间,而官方的图却有 undo tablespace
?下面这段话,描述得很清楚,原因是版本变化!

Rollback Segment(rseg)称为回滚段。Mysql5.6 之前 undo 默认记录到系统表空间(ibdata),如果开启了
innodb_file_per_table ,将放在每个表的.ibd 文件中。5.6 之后还可以创建独立的 undo 表空间,8 之后更是默认打开独立 undo
表空间,最低数量为 2,这样才能保证至少一个 undo 表空间进行 truncate,一个 undo 表空间继续使用。

每个 rollback Segment 中默认有 1024 个 undo log segment,mysql5.5 后 1 个 undo 表空间支持
128 个 rollback Segment。0 号 rollback Segment 默认在系统表空间 ibdata 中,1-32rollback
Segment 在临时表空间,33~128 在独立 undo 表空间中(没有打开则在系统表空间 ibdata 中,这样系统表空间会太大),所以 1
个表空间最多支持 96*1024 个事务,超了就报错啦。

一个 undo log segment 称为 undo log 或 undo slot 或 undo;一个 undo log 对象对应多个 undo
log record,也就是记录的历史版本。(Rollback segment -

THE END
Copyright © 2024 亿华云