MySQL InnoDB引擎行格式、数据页简析

InnoDB行格式、数据页简析

1、MySQL局部性原理:

在InnoDB中,数据会存储到磁盘上,在真正处理数据时需要先将数据加载到内存,表中读取某些记录时,
InnoDB存储引擎不需要一条一条的把记录从磁盘上读出来。

InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB,也就是说,当需要从磁盘中读数据时每一次最少将从磁盘中读取16KB的内容到内存中,每一次最少也会把内存中的16KB内容写到磁盘中。

2、什么是InnoDB的页?

2.1 概念

页是InnoDB管理存储空间的基本单位,一个页的大小默认是16KB。

2.2 页结构

  • File Header
  • Page Header
  • Infimun + Supremum Records
  • User Records
  • Free Space
  • Page Directory
  • File Trailer

2.3 名词解释

名称 中文名 占用空间 简单描述
File Header 文件头部 38字节 页的一些通用信息
Page Header 页面头部 56字节 数据页专有的一些信息
Infimum + Supremum 最小记录和最大记录 26字节 两个虚拟的行记录
User Records 用户空间 不确定 实际存储的行记录内容
Free Space 空闲空间 不确定 页中尚未使用的空间
Page Directory 页目录 不确定 页中的某些记录的相对位置
File Trailer 文件尾部 8字节 校验页是否完整

3、行结构

3.1 行格式

我们看到的数据其实是逻辑上一行一行,当我们想存一条数据的时候,其实还会加很多其他信息。

3.1.1 InnoDB行格式

一行记录可以以不同的格式存在InnoDB中,行格式分别是Compact、Redundant、Dynamic和Compressed行格 式。 我们可以在创建或修改表的语句中指定行格式:

CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称

3.1.2 Compact行格式

3.2 记录的额外信息

服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为三类,分别是:

  • 变长字段长度列表
  • NULL值列表
  • 记录头信息

相当于是提前统计,增加查询效率

这些信息都是通过key:value的形式来进行记录

3.2.1 变长字段长度列表

MySQL支持一些变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、TEXT类型,BLOB类型,这些数据类型 修饰列称为变长字段,变长字段中存储多少字节的数据不是固定的,所以我们在存储真实数据的时候需要顺便把 这些数据占用的字节数也存起来。在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记 录的开头部位,从而形成一个变长字段长度列表。

CHAR是一种固定长度的类型,VARCHAR则是一种可变长度的类型。

VARCHAR(M),M代表最大能存多少个字符。( MySQL5.0.3以前是字节,以后就是字符)

3.2.2 NULL值列表

Compact行格式会把可以为NULL的列统一管理起来,存一个标记为在NULL值列表中,如果表中没有允许存储 NULL 的列,则 NULL值列表也不存在了。

如果字段的值是空的,那么用一个标志来替代,表示它是空值,但是如果用标志来表示,就会占用空间,通过key:value的形式,来记录某一行的某列数据是为空值。

create table t1 (a int primary key,b int,c int,d int,e varchar(20)) engine=InnoDB

假设我们新插入一条数据,a不能为空,b、c、d、e不为空。

再插入一条数据,同样的,a不能为空,b、c、d为空,e不为空,那么,在数据库中的存在形式可以理解为:

1111a
2b

key_a=00000	# 1表示该列为空
key_b=01110	# 0表示该列不为空

如果在数据库中定义了not null,则这个NULL标志位就可以去掉。

3.2.3 记录头信息

除了变长字段长度列表、NULL值列表之外,还有一个用于描述记录的记录头信息,它是由固定的5个字节组成。 5个字节也就是40个二进制位,不同的位代表不同的意思,如图:

名称 大小(单位:bit) 描述
预留位1 1 没有使用
预留位2 1 没有使用
delete_mask 1 标记该记录是否被删除
min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned 4 表示当前记录拥有的记录数
heap_no 13 表示当前记录在记录堆的位置信息
record_type 3 表示当前记录的类型
0表示普通记录,
1表示B+树非叶子节点 记录,
2表示最小记录,
3表示最大记录
next_record 16 表示下一条记录的相对位置

3.2.4 记录真实的数据

记录的真实数据除了我们自己定义的列的数据以外,还会有三个隐藏列:

列名 是否必须 占用空间 描述
row_id 6字节 行ID,唯一标识一条记录
transaction_id 6字节 事务ID
roll_pointer 7字节 回滚指针

实际上这几个列的真正名称其实是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR。

一个表没有手动定义主键,则会选取一个Unique键作为主键,如果连Unique键都没有定义的话,则会为表默 认添加一个名为row_id的隐藏列作为主键。所以row_id是在没有自定义主键以及Unique键的情况下才会存在 的。

3.2.5 行溢出数据

VARCHAR(M)类型的列最多可以占用65535个字节。其中的M代表该类型最多存储的字符数量,如果我们使用 ascii字符集的话,一个字符就代表一个字节,我们看看VARCHAR(65535)是否可用:

mysql> CREATE TABLE varchar_size_demo(
 -> c VARCHAR(65535)
 -> ) CHARSET=ascii ROW_FORMAT=Compact;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type,
not counting BLOBs, is 65535. This includes storage overhead, check the manual. You
have to change some columns to TEXT or BLOBs

因此,创建表的时候,如果定义的某个字段的长度刚好等于最大限制长度,那么就会创建失败,因为数据库除了保存真实的数据以外,还需要保存上述字段的信息。所以,创建字段需要考虑上述字段的大小,可以概述为三部分存储空间:

  1. 真实数据
  2. 变长字段真实数据长度 2字节
  3. NULL值标识 1字节

如果该VARCHAR类型的列没有NOT NULL属性,那最多只能存储65532个字节的数据,因为变长字段的长度占用 2个字节,NULL值标识需要占用1个字节。

3.2.6 数据溢出

记录中的数据太多产生的溢出,因为一页只能存16kb就是16384个字节的数据,如果一行的数据超过这个一页的大小,就存在行溢出的问题。

如何解决呢?

我们可以在一页中存部分数据 + 下一页的地址。

索引页

而在InnoDB中,第一页只保存下一页的地址而不存数据,后面的页存内容。而第一页就是后面所要讲的索引页

3.2.7 总结

Dynamic和Compressed(用得不多)行格式类似于Compact行格式,只不过在处理行溢出数据时有点分歧,它们不会在记录的真实数据处 存储一部分数据,而是把所有的数据都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。另外, Compressed行格式会采用压缩算法对页面进行压缩。

原文地址:https://www.cnblogs.com/liuhuan086/p/13606300.html