一文讲清,MySQL数据库一行数据在磁盘上是怎么存储的?

Hollis

共 2847字,需浏览 6分钟

 · 2021-10-17

数据库给使用者最直观的感觉,就是库、表、行、字段,这些概念都是逻辑上的。前面我们深入讲解了Buffer Pool的内部原理,它的基本存储单位是默认大小为16K的页。每页都保存了一行一行的数据。我们按照数据页为单位把磁盘上的数据加载到内存的缓存页里来,也是按照页为单位,把缓存页的数据刷入磁盘的数据页中。


而我们常常听到数据页、数据区、表空间这些名词,其实这些名词是物理层面上的概念。我们不经要问,库、表、行、字段,这些逻辑上的概念是如何对应到物理层的概念上的呢?我们查询一行数据,是如何找到条数据所在的数据页的呢?


接下来,笔者用几篇文章,讲解下MySQL的表空间、数据区、数据页这些概念,当大家明白搞明白这些东西后,就自然理解上面的问题了。


行格式


行格式即行记录的物理存储格式,决定了这张表数据的物理存储方式,会影响crud性能。


指定行格式

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


InnoDB 包含以下四种行格式

  • Compact
  • Redundant
  • Dynamic
  • Compressed



mysql5.7之前的版本使用的是compact行格式,5.7及以后的版本使用的是dynamic行格式。Compact和Dynamic应用较广泛,compact是目前使用最多的一种,而dynamic是新版本默认的行记录格式。

初识MySQL行数据存储格式

我们这里就以Compact存储格式来讲解。

Compact行格式下,每行数据的存储格式,大概是这样的:

变长字段(记录的长度)列表,null值列表,数据头,col1的值,col2的值,col3的值......

Compact 中一条完整的记录可以被分成'记录的额外信息'和'记录的真实数据' 两部分,其他三种存储存储格式基本大同小异。

变长字段是如何存储的?

MySQL中变长字段长度是不固定的, 比如VARCHAR(50),实际存储的内容可能是"hello",也可能是“hello world"。

所以,我们要读取这类数据字段,必须要知道它的长度。

比如一行数据,它的几个字段为VARCHAR(10), VARCHAR(5)
,VARCHAR(20),CHAR(1),CHAR(1),插入一行数据:hello, ni, hao, a, b。

此时磁盘中存储的行开头的变长字段长度列表,必须存储几个变长字段的长度,需要注意的是,这里是逆序存储的。

行数据存储格式是这样的:

0x03 0x02 0x05 null值列表 头字段 hello, ni, hao, a, b

NULL值列表

null值列表,说的就是一行数据里可能有的字段是选填的,值可以是null。比如name字段,如果允许为null,那么实际存储的时候,要标记出来。

为了节省存储空间,MySQL设计的时候,使用二进制的bit位来存储null值列表。

假如创建了一张表:
CREATE TABLE user (name VARCHAR(10)NOT NULL,    address VARCHAR(20),    gender CHAR(1),    job VARCHAR(30),    school VARCHAR(50))ROW_FORMAT = COMPACT;
user表5个字段,4个变长的,只有name是非NULL的,其他的4个字段都是可以为NULL的。


假如现在插入一行数据:zhangsan NULL M NULL Tsinghua,address和job是NULL,那么它在磁盘上是怎么存储的?

NULL值列表,是这样存储的,你所有允许值为NULL的字段,都会有一个二进制的bit值,bit值为1说明是NULL,如果bit值为0说明不是NULL。

上面插入的那行数据,addressgender、job、school4个字段都允许为NULL,每个字段都会有一个bit位,其中address与job是NULL,所有4个bit位应该是:1010。

而且NULL值列表也是逆序存储的,所以NULL值列表里是0101。

一般NULL值列表是8个bit位的倍数,如果不足8个bit位,则高位补0。

所以现在看来,行数据存储格式是这样的:

0x08 0x08 00000101 头信息 col1的值,col2的值,col3的值......

数据头以及真实数据

行数据的头长度是固定的40bit,第一个bit和第二个bit,都是预留的,暂时没有用到。

记录头信息详细如下表所示,有的暂时没有用到,可以暂时不用深究它。


现在再加上数据头部分,上面的行存储格式就变成这样了,
0x08 0x08 00000101 0000000000000000000010000000000000011001 zhangsan M Tsinghua

刚开始先是变长字段的长度,用16进制表示,然后是NULL值列表,标识哪些值是NULL,接着是40bit的数据头,最后是真实数据。

在读取数据的时候,也会根据变长字段的长度,先读取出长度为8的zhangsan。

然后发现第二个字段是NULL,就不用再读了。

第三个字段是定长的1,就直接读取出gender为M。

第四个字段是NULL,就不用再读了。

第五个字段变长,长度是8,再读取长度为8的Tsinghua。

然而,真正磁盘上存储的时候,那些字符串就是直接存储在磁盘上的吗?

实际上,字符串都是根据数据库指定的字符集编码,进行编码之后再存储的,编码后大概是这样的:

0x08 0x08 00000101 0000000000000000000010000000000000011001 341324 134546 9342345

大家会看到上面,字符串和其他类型的数值最终都会根据数据库字符集编码,变成一些数字和符号存储在磁盘上。

最后,在实际存储一行数据的时候,MySQL还会给每条记录,加入一些隐藏字段。如下表:


如果用户没有指定主键,且表中没有Unique键时才会使用DB_ROW_ID作为主键。

最终,上面那条数据的存储格式,变成这样了:
0x08 0x08 00000101 0000000000000000000010000000000000011001 00000000034C(DB_ROW_ID)00000000036D(DB_TRX_ID) EA000010022B(DB_ROL_PTR) 341324 134546 9342345

到这里为止,我们基本把一行数据在磁盘上是如何存储的讲清楚了。

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号


好文章,我在看❤️

浏览 35
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报