探索MySQL设计的魅力--InnoDB记录存储结构详解

91 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

InnoDB行格式

InnoDB的4种不同类型的行格式,COMPACT、REDUNDANT、DYNAMIC和COMPRESSED

COMPACT行格式

image.png COMPACT行格式分两个部分,一部分存储记录的额外信息,另外一部分存储记录的真实数据。

记录的额外信息

  • 变长字段长度列表:在COMPACT行格式中,所有变长字段的真是数据占用字节数都存放在记录的开头位置,从而形成变长字段长度列表,各变长字段的真实数据占用的字节数按照列的顺序逆序存放
  • NULL值列表:表中没有允许存储NULL的列,则NULL值列表不存在

凡设计,必有考量。 我们来考虑变长字段长度列表与NULL值列表的作用以及作者为什么这么设计。

  • 为什么需要变长字段长度列表? 当存在格式为VARCHAR的列时,需要在行的头部记录VARCHAR的长度,这便是变长字段长度列表存在的原因。
  • 站在设计者的角度,是否存在其他的设计呢? 假如我们不在头部使用变长字段长度列表,我们可以在VARCHAR列使用额外的空间来记录该列数据的大小。例如我们可以规定前n个字节用来记录该数据的大小。这个n个大小就有考究了,理论上,MySQL并没有对VARCHAR能存放的数据大小作一个限制,因此我们这个n并不太好设计;从占用空间的角度考虑,假如我们每个VARCHAR都使用额外的n个字节来记录数据大小,这是十分占用空间的。

又是一个时间换空间的经典案例。

  • 为什么会有NULL值列表? 记录中可能存在NULL值,假如所有列都存实际的NULL值,会十分的占用空间;

记录的真实数据

MySQL会为每个记录默认地添加一些列(隐藏列)

列名是否必需占用空间描述
row_id6字节行ID,唯一标识一条记录
trx_id6字节事务ID
roll_pointer7字节回滚指针

隐藏列补充知识:InnoDB表的主键生成策略:用户设置优先;无则选用不允许存储NULL值的UNIQUE键,否则默认添加row_id的隐藏列作为主键。 注意,由隐藏列补充知识可推理,row_id不是必须生成的。

REDUNDANT行格式

image.png 与COMPACT行格式类似,REDUNDANT行格式也分为两个部分,这其中,记录的额外信息有区别,REDUNDANT行格式记录的是字段长度偏移列表,而COMPACT行格式记录的是变长字段长度列表以及NULL值列表。

  • 字段长度偏移列表:REDUNDANT逆序存储所有字段的长度偏移列表,包括隐藏列
  • NULL值处理:在NUUL值处理方面,REDUNDANT行格式使用字段长度偏移列表在处理NULL值,即在字段长度偏移列表中,将列对应的偏移量值得第一个比特位作为是否为NULL的依据,也被称为NULL比特位。此时,在设计角度考虑,作者使用了一点空间来换取时间。

溢出列

在COMPACT和REDUNDANT行格式中,对于占用存储空间非常多的列,在记录的真实数据处只会存储该列的一部分数据(768字节),而把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20字节存储指向这些页的地址,从而可以找到剩余数据所在的页

为什么使用20字节存储指向这些页的地址?

我没有找到相关的解释,我这边讲一下自己的理解:

  1. 当20字节只用来指向一个数据页时,寻址能力远超当前计算机的存储能力;
  2. 当20字节被用来指向多个数据页时,就需要对20字节进行拆分,寻址能力出现下降。 20字节应该是作者检验过的,寻址能力以及指向页数的平衡点

DYNAMIC行格式和COMPRESSED行格式

  • DYNAMIC在COMPACT基础上,优化了处理溢出列的方式,不会在真实数据处存储溢出数据的前768字节,而是全部存储到溢出页中
  • COMPRESSED行格式在DYNAMIC行格式的基础上,会使用压缩算法对页面进行压缩,以节省空间

实际工作时,我们该如何选择呢?

  • 从性能角度考虑(越好越靠前) REDUNDANT > COMPACT > DYNAMIC > COMPRESSED
  • 从占用资源考虑(占用越少越靠前) COMPRESSED > COMPACT ~= DYNAMIC > REDUNDANT

由此可以看到,MySQL默认的COMPACT其实是性能与占用资源都较好的行格式。实际业务中,我们应该依据实际的场景来选择最佳行格式。