天天看点

mysql的innodb引擎是如何存储数据的?

作者:爱钓鱼的代码搬运工
  • 前言

Mysql的数据是以文件的形式持久化存储在磁盘上的,当我们读取数据的时候,mysql存储引擎就会从这些文件中拿出数据返回给我们,当我们更新写入数据的时mysql存储引擎就会把数据写回这些文件系统 ,那innodb存储引擎到底是如何组织这些数据的呢?

  • 数据库数据文件存储位置

mysql的数据是存储在mysql数据目录中的,我们可以在命令行使用 "show variables like 'datadir' " 来查看数据目录的位置:

mysql的innodb引擎是如何存储数据的?

在数据目录下我们可以看到,每一个数据库都有一个与之对应的同名文件夹

mysql的innodb引擎是如何存储数据的?

mysql 表空间(TableSpace)是指在Mysql数据库中用来存储表数据和索引的区域。表空间从管理上可以分为:系统表空间、独立表空间、撤销表空间和临时表空间。

数据目录下的ibdata1 文件为系统表空间文件,在每个数据库自己的目录中每一张表都对应两个文件:

tablename.ibd 独立表空间文件

tablename.frm 表描述文件

mysql的innodb引擎是如何存储数据的?

.ibd 后缀的文件是每张表对应的独立表空间,独立表空间的主要优势是可以实现更好的性能和更高的可靠性。由于每个表都有自己的数据文件,因此可以单独进行备份和恢复操作,而不会影响其他表的数据。此外,由于每个表的数据和索引都存储在单独的文件中,因此可以更快速地进行查询和更新操作。

数据到底是存储在独立表空间还是系统表空间,可在数据库启动时设置innodb_file_per_table 来控制。

  • mysql 表空间文件解析

mysql数据存储分为5个层次结构:表空间→段→簇→页→行,他们之间的关系如下:

  1. 表空间(Tablespace):表空间是逻辑上的一个容器,用于存储表和索引的数据。一个表空间可以包含多个段。
  2. 段(Segment):段是MySQL中存储数据的基本单位,它是由连续的簇组成的逻辑存储结构。一个段可以包含一个或多个簇。
  3. 簇(Extent):簇是段扩展的最小单位,也叫做区,每个区的大小是1M(64个连续的页)。一个簇通常包含多个页。
  4. 页(Page):页是innodb对磁盘管理的最小单位,每个数据页的大小为16kb,它是用于存储数据的固定大小的块。页的大小可以根据存储引擎和配置进行调整。
  5. 行(Row):行是表中的一条记录,它包含了一组字段(列)的数据。行存储在页中,每个页可以包含多条行。

系统表空间通常由以下段组成:

  1. 数据段(Data Segment):存储表的实时数据,每个系统表空间可以包含一个或多个数据段,其中的每个数据段可以对应一张表或者多个表的数据。
  2. 索引段(Index Segment):索引段存储了表的索引数据。MySQL(innodb引擎)使用B+树索引结构,每个索引对应一个索引段。
  3. 回滚段(Rollback Segment):回滚段用于实现事务的回滚操作。当事务需要回滚时,相关的修改操作可以通过回滚段进行撤销。
  4. 系统段(System Segment):系统段包含了InnoDB存储引擎的元数据信息,如表空间的相关配置和统计信息。

独立表空间由以下段组成:

  1. 数据段:每个独立表空间都至少包含一个数据段,用于存储表的数据。数据段中包含了表的行数据,包括表的所有列和数据行的元数据信息。如果表使用了压缩功能,数据段中还会包含压缩后的数据。
  2. 索引段:每个表可以有多个索引,每个索引都有一个对应的索引段。索引段中包含了索引的所有键值和指向数据行的指针,用于支持快速的索引查询操作。
  3. 回滚段(Rollback Segment):回滚段用于实现事务的回滚操作。当事务需要回滚时,相关的修改操作可以通过回滚段进行撤销。
  4. 描述符段(Descriptor Segment):描述符段存储了关于独立表空间的描述信息,包括表空间的结构和属性等。

页的数据结构为:

mysql的innodb引擎是如何存储数据的?

页的头部保存了两个指针,分别指向前一个页和后一个页,头部还有页的类型信息和用来唯一标识页的编号。根据这个指针分布可以想象到页链接起来就是一个双向链表。双向链表之间的物理位置可能会离得非常远,当遇到范围查询的适用场景的时候,就会定位到最左边和最右边的记录,然后沿着双向链表一直扫描,而如果这其中的两个页面物理上离得特别远,就会成为随机I/O,由于磁盘和内存的速度相差了几个数量级(磁盘寻道、半圈旋转、数据传输),因此随机I/O的速度是非常慢的,所以应该尽量让链表中逻辑上相邻的页物理上也相邻,这样进行范围查询的时候就可以使用顺序I/O。

为了尽量让链表中逻辑上相邻的页物理上也相邻,因此引入区的概念,一个区就是物理上连续的64个页,即64*16=1MB。并且在数据量较大的时候,会以区为单位为索引分配空间而不是以页为基本单位,甚至数据特别多的时候,会一次性分配多个连续的区,虽然可能造成空间浪费,但是消除了很多的随机I/O,大大地提高了性能。

一开始生成页的时候并没有User Records这个部分。每当我们插⼊⼀条记录,都会从Free Space部分,也就是尚未使⽤的存储空间中申请⼀个记录⼤⼩的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使⽤完了,如果还有新的记录插⼊的话,就需要去申请新的页了。

  • 总结

创建一个索引,通常会对应一个B+树,也会产生两个段,一个是数据段,一个是索引段,数据段即对应B+数的叶子节点段,索引段即对应B+树的非叶子节点段。段并不是物理上连续的区,而是一个逻辑的数据块,由若干个零散的页面和一些完整的区组成的。B+树中的节点与段之间的对应关系是多对多的关系。一个段可以包含多个B+树节点,而一个B+树节点也可以跨越多个段。每个区又是物理上连续的64个页组成,页和页之间又是双向链表。

一个很简单的B+树如下所示:

mysql的innodb引擎是如何存储数据的?

继续阅读