揭秘MySQL数据布局:探索记录结构的奥秘

2023年 8月 25日 80.7k 0

随便聊聊

最近大致看完了小孩子老师的《 MySQL 是怎样运行的》,不禁纳头便拜,对大佬的敬仰之情如同涛涛江水连绵不绝,人与人的差距真是比人跟猪的差距还要大。感慨之余,又懊恼书中的细节是如此之多,容易被花花细节迷了眼,完全没留下一点印象。我觉得对于普通后端程序员来说,最重要的是要对 MySQL 的整体架构和执行原理有一个基础的认知,并不需要记住许多具体的实现细节,因此这篇文章将会对 MySQL 中的数据组织方式进行一个梳理,同时并不会对于 MySQL 的底层进行细致描述。,例如:某个属性所在的某个字节的具体位置等等(如有需要,请去翻书。 ps:主要是因为我看了半天实在是记不住 不是 )

"记录:数据库世界的基石

对于记录大家应该已经相当熟悉了,对于数据库而言,我们最终的目的就是为了对存储在数据库中的记录进行增删改查等操作,同时记录也是 MySQL 中最基本的数据结构。在 MySQL 中,不同的存储引擎可能采用不同的记录结构,就算是相同的存储引擎为了满足不同的需求也设计了许多不同的记录结构(例如 InnoDB 引擎就包含 Compact 、Redundant 、Dynamic 和 Compressed 四种不同的记录结构),这里我们就采用 InnoDB 的 Compact 记录结构作为例子。毕竟在日常使用的时候我们实际上很少去改动记录结构。

简单明了:Compact 记录的构造

compact行格式.png

从上图中很容易看出,Compact 记录结构主要有两大部分组成,前面一部分是这条记录的一些额外信息,后面一部分是这条记录所需要保存的真实数据。这个结构本身很符合直觉,没有什么好说的。如果我们的表结构中每一列全部都是定长字段,例如 int、char(10) 等等,那么只需要把数据直接保存到后面的真实数据部分即可。但是很可惜的是,数据库中还有不少无法确定实际长度的变长字段,例如 varchar(10) 、blog 等等,这类字段的实际长度可能发生变化。对于 varchar(10) 这样的列还好办一些,反正它可能保存的字符数量是 0~10 ,我们如果浪费一点空间,可以直接按照上限给它分配空间,多少还能存的进去(当然, MySQL 中实际上有更好的方式进行存储,这个后面会介绍),但是对于 blog 这样的字段,我们根本就不能确定它的上限(这么说实际上也不严谨, blog 最多可以存储 65535 个字节,但是这是一个非常大的值,如果按照上限分配空间所需要的空间成本就太难接受了)。

变长字段的“奇淫巧技”

让我们先来提取一下上面遇到的问题:

  • 对于较短一些的变长记录,我们可以无脑直接按照上限分配,但是我们不知道这个记录的实际占用长度。
  • 对于较长的变长记录,无法直接存在记录的真实数据中。
  • OK,明确了问题,接下来就对问题进行逐个击破:

    第一个问题实际上很好解决,我们想要知道一个变长记录的实际长度,一个简单的方法就是直接将这个记录的实际长度记录下来就好了,在 Compact 记录的额外信息中,有一个变长字段长度列表。这个字段就是用来保存本记录所有变长字段的实际长度。具体是怎么存储的在本文中就不进行详细的说明了,只需要知道保存变长字段的时候会把该字段的实际长度也保存下来就行了,如果想知道的话可以翻阅《 MySQL 是怎样运行的》。

    接下来还剩下的第二个问题就比较棘手了,但是对于这一类不确定长度的问题有一个通用的解决方案--链表!当变长字段内容比较多的时候,在记录的真实数据处存放的就不仅仅是真实数据的值了,同时还会保存一个指针,指向一块专门为变长字段分配的地址。如下图所示
    变长字段的存储方式.png
    这样,我们就解决了字段太长,无法直接存放在真实数据字段中的问题

    谁是“空白”王者:NULL 字段的魔法

    在一条记录中,某些列可能出现 NULL 值,当该列值为 NULL 时,我们就不应该访问对应的真实记录。所以应该将这些信息进行保留,当该记录中存在可能为 NULL 值的列时(只要列的值可能为 NUL L就需要维护这个列表),将会在额外信息中创建一个 NULL 值列表,保存对应的 NULL 值信息。当然,如果一个列中所有的列都不可能为 NULL 时,自然也就不需要创建 NULL 值列表了。

    真实数据中的卧底

    在记录的真实数据中,除了我们自己定义的列的数据以外,MySQL 为了实现一些其他目的,会为每个记录增加一些隐藏列:
    对于我们创建的表来说,记录的真实数据除了 c1 、c2 、c3 、c4、... 这几个我们自己定义的列的数据以外,MySQL会为每个记录默认的添加一些列(也称为隐藏列),具体的列如下:

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

    其中 row_id 是一个非必须的列,它的作用是当我们没有为表指定主键,或者唯一列的时候用于充当隐藏主键。所以只要当我们指定了主键或者唯一列的时候自然就不需要创建 row_id 。剩下的两个列暂时只要看一下就好。

    小结一下:对于MySQL中的每一条记录有以下几个知识点:

  • 当列为定长字段时,会将该列的值直接记录在真实数据部分
  • 当列为不定长字段时,分为两种情况
    - 当字段较短时,将真实值记录在真实数据部分,同时在额外信息中保存下该字段的真实长度
    - 当字段较长时,真实数据部分将保存该字段的开头部分数据,以及一个指针,用于指向该字段的剩余数据
  • 当记录中可能有字段为空值时,会在额外数据中维护一个NULL值列表,保存字段是否为NULL的信息
  • 在记录的真实数据部分,除了我们创建的列以外,MySQL会额外添加一些隐藏列
  • 相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论