1. 行格式
对数据页中的每一行数据,它是如何存储的的。其实这里涉及到一个概念,就是行格式。我们可以对一个表指定他的行存储的格式是什么,比如使用COMPACT 格式。
CREATE TABLE table_name(cloumns)ROW_FORMAT=COMPACT
ALTER TABLE table_name ROW_FORMAT=COMPACT
可以在建表的时候,就指定一个行存储的格式,也可以后续修改行存储的格式。这里指定了一个CONPACT 行存储格式,在这种存储格式下,每一行数据他实际存储得到时候,大概格式类似下面这样。
变长字段的长度列表,null 值列表,数据头,column01 值, column02 的值, column0n 的值。
对于每一行数据,其实存储的时候都对这行数据进行一定的描述,然后再放上他这一行数据每一列的具体的值,这就是所谓的行格式。除了COMPACT 以外,还有其他几种行存储格式,基本都大同小异。
2.存储VARCHAR
在MYSQL 里有一些字段的长度是变长的,是不固定的,比如VARCHAR(10) 之类的字段,实际上他里面存放的字符串,可能是“a” 这么一个字符串。
对于VARCHAR 这种变长字段,在磁盘上到底是如何存储的呢。
引入变长字段的长度列表,解决一行数据的读取问题。也就是说,你在存储”hello a a “ 这行数据的时候,要带上一些额外信息,比如第一块就是他里面的变长字段的长度列表。hello 是 VARCHAR(10) 类型的变长字段的值,那么这个”hello” 字段值的长度是5 ,十六进制就是 0x05,所以此时会在 “hello a a “ 前面补充一些额外信息,首先就是变长字段的长度列表,这行数据文件里存储的时候,其实类似如下的格式:0x05 null 值列表 数据头 hello a a 。
多个变长字段,如何存放他们的长度呢。比如一行数据有VARCHAR(10) Varchar(5) VARCHAR(20) CHAR(1)CHAR(1),一共 5个字段,其中 三个是变长字段,此时假设一行数据是这样的:hello hi hao a a
也就是说先存放VARCHAR(20) 这个字段的长度,然后存放VARCHAR(5) 这个字段的长度,最后存放VARCHAR(10) 这个字段的长度。
现在 hello hi hao 三个字段的分别是 0x05 0x02 0x03 ,但是实际存放在变长字段列表的时候,是逆序的,所以一行数据实际存储可能是下面这样的:
0x03 0x02 0x05 null 值列表 头字段 hello hi hao a a
3. 如何存储Null 值
一行数据里的NuLL 值是不会按照字符串的方式放在磁盘上浪费空间的,NULL 值是以二进制bit 位来存储的。
假设一行数据里有多个字段的值都是NuLL ,那么这多个字段的NULL, 就会以bit 位的形式存放在NULL 值列表里。
4. 案例—一行数据的磁盘存储格式
假设有一张表,建表语句如下:
CREATE TABLE customer(
name VACHAR(10) NOT NULL,
address VACHAR(20),
gender CHAR(1),
job VACHRAR(30),
school VACHAR(50)
)ROW_FORMAT=COMPACT
假设我们需要存储jack NULL m NULL xx_school
,它的行格式是什么呢。
先回顾一下,一行数据在磁盘上的存储格式如下:
变长字段的长度列表,null 值列表,数据头,column01 值, column02 的值, column0n 的值
先看看变长字段长度列表应该放什么东西,它一共有4个变长字段。但是要区分一个问题,那就是如果某个变长字段的值是NULL, 就不用在变长字段长度列表里存放它的值长度。所以在上面那行数据中,只有name 和 school 两个变长字段是有值的,把他们的长度按照逆序放在变长字段长度列表中就可以了,如下所示:
0x09 0x04 NULL值列表 头信息 column01 值, column02 的值, column0n 的值
接下来看Null 值列表,如果bit 值是1说明是NULL ,如果不是0说明不是NULL。比如上面4个字段都允许为NULL,每个都有一个bit位,然后其中2个字段是null,2个字段不是null,所以bit 位应该是:1010。
但是存放NULL值列表的时候,也是按照逆序存放的,而且他是8个bit位的倍数,如果不足8位,高位补0。整体这一行数据格式如下:
0x09 0x04 00000101 头信息 column1=value1 column2=value2 columnN=valueN
5. 磁盘上的一行数据如何读取出来的
上节的磁盘数据存储格式:
0x09 0x04 00000101 头信息 column1=value1 column2=value2 columnN=valueN
首选先把变长字段长度列表和NULL值列表读取出来,然后通过综合分析,就知道几个字段是NULL。此时就可以从变长字段长度列表中解析出来不为NULL 的变长字段的值长度,然后也知道哪几个字段是NULL值。
此时就可以从实际的列值存储里,把每个字段的值读取出来。如果是变长字段的值,就按照它的值长度来读取,如果是NULL,就知道他是个NULL ,没有值存储。如果是定长字段,就按照定长长度来读取,这样就可以把一行数据全部读取出来。
6. 头信息
每一行数据存储的时候,还得有40个bit 位的数据头,这个数据头来描述这样数据的。
bit 位 | 描述 |
---|---|
1-2 | 预留位 |
3 | delete_mask,标识这样数据是否被删除(数据库的数据不是立即清理 ) |
4 | min_rec_mask,B+树里每一层非叶子节点最小值都有这个标记 |
5-8 | n_owned,后续说明 |
9-21 | heap_no,在记录堆的位置 |
22-24 | record_type,这行数据的类型 (0代表普通类型,1代表B+树非叶子节点,2代表是最小值数据,3代表最大值数据) |
35-40 | next_record,下一条数据的指针 |
7.真实数据怎么存储
上边说的例子,有一行数据是”jack NULL m NULL xx_schhol”,那么它真实存储大致如下:
0x09 0x04 00000101 0000000000000000000010000000000000011001 jack m xx_school
刚开始是它的变长字段的长度,用十六进制存储,然后是NULL值列表,指出了谁是NULL,接着是40个bit位的数据头,然后是真实的数据值。
但是实际上字符串是根据数据库指定的字符集编码,编码后进行存储的。所以大致看起来一行数据如下所示:
0x09 0x04 00000101 0000000000000000000010000000000000011001 616161v6366320 6262626262
除了上述的字段外,会在真实的数据部分,加入一些隐藏字段。
首先有一个DB_ROW_ID 字段,行唯一标识。如果我们没有指定主键和unique key唯一索引的时候,内部自动加一个ROW_ID 作为主键。
接着 是一个DB_TRX_ID 字段,这是事务ID。
最后是DB_ROLL_PTR字段,是回滚指针,用来事务回滚的。
加上这几个隐藏字段后,实际上一行数据可能看起来如下所示:
0x09 0x04 00000101 0000000000000000000010000000000000011001 00000000094C(DB_ROW_ID) 00000000032D(DB_TRX_ID) EA000010078E(DB_ROL_PTR) 616161v6366320 6262626262
8.什么是行溢出
之前已经了解到,每一行都是放在一个数据页里,这个数据页默认大小是16KB,万一一行数据大于一个数据页的大小怎么办。
这个时候实际上会在那一页里存储这行数据,仅仅包含一部分数据,同时包含一个20个字节的指针,指向了其他的一些数据页,那些数据页用链表串连起来,存放这行数据,这就叫行溢出。