MySQL InnoDB 存储原理

MySQL InnoDB 存储原理

简介

  • mysql InnoDB 存储引擎

目录结构

  • mysql InnoDB存储引擎的数据以目录文件形式存储在磁盘中;

  • 每个db 都会在 /var/lib/mysql/ 目录里面创建一个以 db 为名的目录;

  • 每个db目录下包含:

    • db.opt:db选项,记录当前数据库db的默认字符集和字符校验规则;
    • table1.frm:表table1结构文件,保存每个表的元数据信息的,主要包含表结构定义;
    • table1.ibd:表table1数据文件,表数据既可以存在共享表空间文件(文件名:ibdata1)里,也可以存放在独占表空间文件(文件名:表名字.ibd)。这个行为是由参数 innodb_file_per_table 控制的,若设置了参数 innodb_file_per_table 为 1,则会将存储的数据、索引等信息单独存储在一个独占表空间,从 MySQL 5.6.6 版本开始,它的默认值就是 1 了,因此从这个版本之后, MySQL 中每一张表的数据都存放在一个独立的 .ibd 文件。
1
2
3
4
[root@xiaolin ~]#ls /var/lib/mysql/my_test
db.opt  
table1.frm  
table1.ibd

表空间文件结构

  • 表数据文件(.ibd)又叫表空间文件;

  • 表空间由

    • 行(row):

      • innodb中表记录的每行数据存储为一行;
      • 行是innodb数据存储基本单元;
      • 一行记录除了 TEXT、BLOBs 类型的列,最大为 65535(64K) 字节;
    • 页(page):

      • 页是innodb的读写单元,innodb每次读写都是一个完整的page;
      • 每个页可以包含多个row;
      • 每个页默认大小16KB;
      • 页之间通过双向链表组织起来,逻辑上连续,物理上可能不连续;
      • 页通过
      • 页分为:
        • 数据页:包含row记录;
        • undo 日志页:;
        • 溢出页
    • 区(extent):

      • 当表中数据量大时,为某个索引分配空间不按照页为单位分配了,而是按照区(extent)为单位分配;
      • 每个区中包含一系列相邻的页,使用顺序 I/O 了;
      • 每个区的大小为 1MB;
    • 段(segment):

      • 多个区(extent)组成段;

      • 段分为:

        • 索引段:存放 B + 树的非叶子节点的区的集合;
        • 数据段:存放 B + 树的叶子节点的区的集合;
        • 回滚段:存放的是回滚数据的区的集合,之前讲事务隔离 (opens new window)的时候就介绍到了 MVCC 利用了回滚段实现了多版本查询数据。
      • 这些段共同组成表空间文件;

行存储结构

InnoDB 的表行存储提供有 4 种行格式:

  • Redundant: MySQL 5.0 版本之前用的行格式,不是一种紧凑的行格式;
  • Compact:一种紧凑的行格式,可以让一个数据页中可以存放更多的行记录,是MySQL 5.1 版本之后的默认行格式;
  • Dynamic:
  • Compresse:

Compact格式行结构

记录的额外信息

  • 变长字段长度列表

    • 记录varchar, text, blob等变长列的字节长度;
    • 按照列的顺序逆序存放
    • 不需要保存值为 NULL 的变长字段的长度;
    • 逆序存放是为了从记录头信息开始,位置靠前的记录的真实数据和数据对应的字段长度信息可以同时在一个 CPU Cache Line,提高效率;
  • NULL 值列表

    • 记录可能会存储 NULL列是否是NULL值;
    • 每个列对应一个二进制位(bit),1代表NULL,0为非NULL;
    • NULL 值列表必须用整数个字节的位表示,小于9个NULL列,用1个字节,否则用多个字节;
    • NULL 值列表也不是必须的,当所有字段都定义成 NOT NULL 时,不需要NULL值列表;
  • 记录头信息

    • 记录头信息中包含的内容很多;

    • delete_mask:标识此条数据是否被删除,执行 detele 删除记录的时候,并不会真正的删除记录,只是将该条记录的 delete_mask 标记为 1;

    • next_record:下一条记录的位置。指向的是下一条记录的「记录头信息」和「真实数据」之间的位置;

    • record_type:表示当前记录的类型。

      • 0:表示普通记录;
      • 1:表示B+树非叶子节点记录;
      • 2:表示最小记录;
      • 3:表示最大记录;

隐藏字段

  • row_id:

    • 建表时未指定主键和唯一约束列,则innodb将自动添加row_id,否则没有row_id;
    • row_id不是必需的,有的话占用 6 个字节;
  • trx_id:事务id

    • 表示这条记录是由哪个事务生成的;
    • trx_id必需,占用 6 个字节;
  • roll_pointer:回滚指针

    • 记录上一个版本的指针;
    • roll_pointer 是必需的,占用 7 个字节。

行溢出

  • 如果一个数据页存不了一条记录,InnoDB 存储引擎会自动将溢出的数据存放到「溢出页」中;
  • 发生行溢出时,在记录的真实数据处只会保存该列的一部分数据,而把剩余的数据放在「溢出页」中,然后真实数据处用 20 字节存储指向溢出页的地址,从而可以找到剩余数据所在的页;

数据页结构

数据页包括七个部分:

  • File Header(文件头):记录页信息, File Header 中有两个指针,分别指向上一个数据页和下一个数据页;

  • Page Hader(页头):页状态信息;

  • 最大最小记录:页中最大记录,最小行记录;

  • User Records:一系列数据行记录;

    • 记录按照「主键」顺序组成单向链表;
  • FreeSpace: 页中还未被行记录使用的空闲空间;

  • Page Directory:页目录,记录的索引作用;

  • FileTailer:校验页是否完整;

多个数据页通过FileHeader中的2个指针组成双向链表。

页目录

页目录创建的过程如下:

  1. 将所有的记录划分成几个组,这些记录包括最小记录和最大记录,但不包括标记为“已删除”的记录;
  2. 每个记录组的最后一条记录就是组内最大的那条记录,并且最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned 字段(上图中粉红色字段)
  3. 页目录用来存储每组最后一条记录的地址偏移量,这些地址偏移量会按照先后顺序存储起来,每组的地址偏移量也被称之为槽(slot),每个槽相当于指针指向了不同组的最后一个记录

B+树结构

聚簇索引

参考

  1. 从数据页的角度看 B+ 树 | 小林coding
updatedupdated2024-05-102024-05-10