InnoDB存储引擎的数据存储方式(存储模式)

逻辑存储结构

在InnoDB存储引擎中,所有数据都存放在表空间(tablespace)中,表空间由段(segment)、区(extent)、页(page)、行(Row)组成,它们的关系如下图:
在这里插入图片描述

表空间(tablespace)

在MySQL中,所有InnoDB存储引擎表中的数据都存储在表空间中。如果用户启用了innodb_file_per_table,那么每张表内的数据可以存储在一个单独的表空间文件(称为独立表空间文件)中,如果没有启用,那么数据都会存储在共享表空间文件中(默认情况下的ibdata0ibdata1文件)。在MySQL 5.7中,innodb_file_per_table默认是启用的:

mysql> show variables like 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.05 sec)

独立表空间文件存放的只是这张表的数据、索引和插入缓冲Bitmap页。对于回滚信息、插入缓冲索引页、事务信息、二次写缓冲依然存放于共享表空间文件。

段(segment)

表空间由各个段组成,常见的段类型有:数据段、索引段、回滚段。
由于InnoDB表采用的是聚簇索引,所以数据段可以看成是B+树的叶子节点,索引段可以看成是B+树的非索引节点。

区(extend)

一个段由多个区组成,区由多个连续页组成,每个区的大小为1MB,默认情况下,每个页的大小为16KB:

mysql> show variables like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.05 sec)

即一个区中一共有64个连续页。用户可通过innodb_page_size参数设置每个页的大小。

默认情况下,用户在创建一张InnoDB表后,该表对应的独立表空间文件为96KB,在每个段开始时会先用32个碎片页来存放数据,使用完这32个页后才是64个连续页的申请。这么做是考虑到有些表的数据相对来说是比较少的,可以节省磁盘空间,因为申请64个页(即1个区)需要1MB空间。

页(page)

页是InnoDB磁盘管理的最小单位,默认大小为16KB。常见的页类型有:

  • 数据页(B-tree Node
  • undo页(undo Log Page
  • 系统页(System Page
  • 事务数据页(Transaction system Page
  • 插入缓冲位图页(Insert Buffer Bitmap
  • 插入缓冲空闲列表页(Insert Buffer Free List
  • 未压缩的二进制大对象页(Uncompressed BLOB Page
  • 压缩的二进制大对象页(compressed BLOB Page
行(Row)

InnoDB存储引擎将数据按行进行存放,每个页最多存放7992行记录(16KB除以2~200),InnoDB存储引擎提供了CompactRedundantCompressedDynamic四种格式来存放行记录数据,用户可通过命令show table status like 'table_name'来查看该属性:

mysql> show table status like 'emp';
+------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
| Name | Engine | Version | Row_format | Rows | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Create_time         | Update_time | Check_time | Collation          | Checksum | Create_options | Comment |
+------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
| emp  | InnoDB |      10 | Dynamic    |    6 |           2730 |       16384 |               0 |            0 |         0 | NULL           | 2019-08-10 21:07:05 | NULL        | NULL       | utf8mb4_general_ci | NULL     |                |         |
+------+--------+---------+------------+------+----------------+-------------+-----------------+--------------+-----------+----------------+---------------------+-------------+------------+--------------------+----------+----------------+---------+
1 row in set (0.08 sec)

其中Row_format就是行记录格式。下面我们来深入探讨InnoDB的行记录格式是如何实现的。

行记录格式
Compact

Compact行记录格式是在MySQL 5.0引入的,设计目标是高效地存储数据,每行数据的存储方式如下:
在这里插入图片描述

  1. 变长字段长度列表
    Compact行记录格式首部是一个非NULL的变长字段长度列表,按照列的顺序逆序放置,若列的长度小于255字节,则用1字节表示,若大于255个字节,用2字节表示。变长字段长度列表最大只能为2字节,因此VARCHAR类型理论最大长度限制为65535字节)。变长字段长度列表的实际占用大小由变长列的数量及其属性决定。

  2. NULL标志位(1字节)
    NULL标志位记录了该行数据是否有NULL值,有则用1表示,该标志位占用1个字节

  3. 记录头信息(5字节)

    名称大小(位)描述
    未知2未知
    deleted_flag`1该行是否被删除
    min_rec_flag`1该行是否是预先被定位为最小的记录
    n_owned`4该记录拥有的记录数
    heap_no`13索引堆中该条记录的排序记录
    record_type`3记录类型,000为普通,001为B+树节点指针,010为Infimum,011为Supremum,其它为保留用途
    next_record`16页中下一条记录的相对位置,InnoDB通过该字段将各个行数据通过链表的方式串联在一起
  4. 列数据
    最后的部分就是存储每个列的数据,需要注意的是NULL不占用任何空间,即NULL除了占有NULL标志位以外实际存储不占有任何空间。此外,除了实际存储数据的列以外,还存在两个隐藏列:事务ID列(6字节)、回滚指针列(7字节)。如果没有定义主键,每行还会自动添加一个6字节的rowid列。

行溢出的处理

对于TEXTBLOB或者长度比较大的VARCHAR这种存储的数据量较大的列,因为超出了页的大小(16KB),也就是发生了行溢出。
Compact/Redundant两种行记录格式下,InnoDB会将溢出的数据存储在Uncompress BLOB类型的页中,没有溢出的数据部分依旧保存在数据页(B-Tree Node)中。

在这里插入图片描述
B-tree Node负责保存一行数据前768字节,之后记录了一个偏移量,指向行溢出页。
因为InnoDB是采用B+树进行索引组织的,所以每个页中至少应当有两行记录(不然每个页存储一行数据就是链表结构了),因此如果页中只能存放一条记录,那么InnoDB会将行数据存放到溢出页中。

Dynamic / Compressed

Dynamic / Compressed文件格式统称为Barracuda文件格式,这两种格式采用了完全行溢出的处理方式:数据页中只存储20个字节的指针,实际数据存放在Off Page中:
在这里插入图片描述
Compressed行记录还会使用zlib算法对大长度数据进行压缩,能够节省存储空间。

数据页(B-tree Node)结构

InnoDB数据页由7个部分组成

  • File Header(文件头),占用38字节
  • Page Header(页头),占用56字节
  • Infimum / Supremum Records
  • User Records(用户记录 / 行记录)
  • Free Space(空闲空间)
  • Page Directory(页目录)
  • File Trailer(文件结尾信息),占用8字节

File HeaderPage HeaderFile Trailer大小是固定的,用来标记该页的的一些信息。User RecordsFree SpacePage Directory为实际的行记录存储空间,大小是动态的。

File Header组成结构

File Header负责记录页的头信息,占用38字节:

  1. FIL_PAGE_SPACE_OR_CHECKSUM(4字节):页的checksum的值
  2. FIL_PAGE_OFFSET(4字节):页的偏移值,即该页在表空间的位置
  3. FIL_PAGE_PREV(4字节):当前页的上一个页(B+树的叶子节点通过双向列表连接在一起)
  4. FIL_PAGE_NEXT(4字节):当前页的下一个页
  5. FIL_PAGE_LSN(8字节):代表该页最后被修改的日志序列位置
  6. FIL_PAGE_TYPE(2字节):页的类型。当值为0x45BF代表当前页是数据页,0x000A表示是BLOB页,0x0003表示是索引节点…
  7. FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID(4字节):当前页属于哪个表空间
Page Header组成结构
  1. PAGE_N_DIR_SLOTS(2字节):在页目录(Page Directory)中的槽(Slot)数。
  2. PAGE_HEAP_TOP(2字节):堆中第一个记录的指针
  3. PAGE_N_HEAP(2字节):堆中记录数,第15位表示行记录格式
  4. PAGE_FREE(2字节):指向可重用空间的首指针
  5. PAGE_GARBAGE(2字节):已删除记录的字节数
  6. PAGE_LAST_INSERT(2字节):最后插入记录的位置
  7. PAGE_DIRECTION(2字节):最后插入的方向
  8. PAGE_N_DIRECTION(2字节):一个方向连续插入记录的数量
  9. PAGE_N_RECS(2字节):该页中记录的数量
  10. PAGE_MAX_TRX_ID (8字节):修改当前页的最大事务ID
  11. PAGE_LEVEL(2字节):当前页在索引树(B+ tree)位置
  12. PAGE_INDEX_ID(8字节):索引ID,表示当前页属于哪一个索引
  13. PAGE_BTR_SEG_LEAF(10字节):B+树数据页非叶节点所在段的segment header
  14. PAGE_BTR_SEG_TOP(10字节):B+树数据页所在段的segment header
Infimum / Supremum Record

在InnoDB引擎中每个页都有两个虚拟行记录用来限定记录的边界Infimum记录了比该页任何主键值都要小的值,Supremum Record记录了比任何可能大的主键值还要大的值,这两个字段在页创建时被创建并且不会被删除,占用大小和行记录格式相关。Infimum在行记录首部,Supremum Record在行记录尾部。

User Record / Free Space

User Record实际存储行记录的内容
Free Space即空闲空间,也是采用“链表”的方式存储,当前页一条记录被删除后,该空间就会被加入到空闲链表中。

Page Directory

Page Directory(页目录)存放了记录的相对位置(不是偏移量),页目录里面的这些记录指针称之为Slots(槽)。对于InnoDB来说,并不是每个记录都拥有一个唯一的槽,因为每个槽里面可能包含了多条记录。Slots中的记录按照索引键值存放,这样可以利用二叉查找快速找到记录的指针。

需要注意的是,B+树本身并不能找到具体的一条记录,只能找到该记录所在的页。数据库通过磁盘IO把页读入到内存,然后再通过页目录进行二叉查找,二叉查找在内存中的速度很快,一般会忽略这部分的时间。

File Trailer

为了检测页是否完整写入了磁盘,InnoDB存储引擎的页中设置了File Trailer部分。
File Trailer占用8个字节,前4个字节表示该页的checksum值,最后4个字节和FileHeader中的FIL_PAGE_LSN相同。通过与这个值进行比较,来保证当前页是完整的。默认情况下InnoDB每从磁盘中读取页就会检验这个页的完整性。

参考资料

《MySQL技术内幕(InnoDB存储引擎)》

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值