一、一行记录的格式
首先介绍“行格式”这个概念,我们可以通过行格式来指定表的行存储的格式是什么样的。常见的行式有以下4种:
(1)REDUNDANT
(2)COMPACT
(3)DYNAMIC
(4)COMPRESSED
各种表格式的特点及缺点:
以COMPACT为例,在这种格式下,每一行数据在实际存储时,打给的格式类似于下面这样:
变长字段的长度列表,null值列表,数据头,column01的值,column02的值,…,column0n的值
注意实际存储的时候没有逗号分隔,这里只是为了方便说明。
1. 变长字段的长度列表
MySQL的字段是支持变长字符串的,如果直接保存到磁盘文件中,变长字段如何保存的呢,我们怎么知道每个字段的起始位置呢。其实,MySQL中是通过加上变长字段的长度列表
这个附加信息来解决的。例如有下面这两行记录:
name(varchar(100)) | website(varchar(100)) |
baidu | www.baidu.com |
tencent | www.tencent.com |
首先"baidu"这个字符串长度为5,就用5的十六进制
0x5来表示,"www.baidu.com"这个字符串的长度为13,就用0xd来表示。那么这行记录的COMPACT格式就是这样的:0x5 0xd null值列表 数据头 baidu www.baidu.com。同理第二行记录是这样保存的:0x7 0xf null值列表 数据头 tencent www.tencent.com。两行记录连在一起就是:
0x5 0xd null值列表 数据头 baidu www.baidu.com 0x7 0xf null值列表 数据头 tencent www.tencent.com
这样我们可以很容易的根据变长字段列表来读取每个字段了。
2. NULL 值字段列表
有的表示允许一行记录中某些字段为null的,但是实际存储的时候,不可能用一个"NULL"字符串来表示空值。MySQL是通过二进制的bit位来保存NULL值字段的
。例如有下面这两行记录:
name(varchar(100) not null) | address(varchar(100)) | gender(char(1)) | school(varchar(100)) | job(varchar(10)) |
tom | null | 1 | null | teacher |
jerry | beijing | 1 | null | student |
其中,name不允许为null,其他字段都允许。第一行记录有两个空值,那么就用 1010来表示,1代表这个位置的值为空,0代表不空。实际存储的时候是逆序
的,也就是存储为0101。所以第一行记录在磁盘上保存为:0x3 0x7 0101 数据头 tom 1 teacher。同理第二行记录保存为:0x5 0x7 0x7 0100 jerry beijing 1 student。两行记录连在一起就是:
0x3 0x7 0101 数据头 tom 1 teacher 0x5 0x7 0x7 0100 jerry beijing 1 student
这样我们就可以通过null值字段列表来判断值为空的字段了。
结合上面的变长字段列表和这里的NULL值字段列表,我们就知道从磁盘上读取一行数据的过程如下:
- 读取解析变长字段列表和NULL值字段列表
- 如果一个字段是NULL值,那么就直接设置为NULL
- 如果一个字段不是NULL,并且是定长字段,那么直接读取固定长度的内容
- 如果一个字段不是NULL,并且是变长字段,那么根据变长字段列表中这个字段的长度来读取内容
这样就得到了最终的一行记录的所有字段的值了。
3. 数据头
数据头占40个bit位
,它是用来描述这行数据的信息的:
- 第1个bit位和第2个bit位是预留的,没有实际意义
- 第3个bit为是
delete_mask
:表示这行数据是否被删除 - 第4个bit位是
min_rec_mask
:表示B+树中每一层非叶子节点里的最小值 - 第5-8的bit位是
n_owned
- 第9-21的bit位是
heap_no
:表示这行数据在记录堆里的位置 - 第22-24的bit位是
record_type
:表示这行数据的类型。0代表普通类型,1代表B+树的叶子节点类型,2代表是最小值数据,3代表是最大值数据 - 第25-40的bit位置
next_record
:是指向下一条数据的指针
二、磁盘上如何存储一行记录
假设我们这里有一行数据是"jack NULL m NULL xx_school",那么它的行格式如下:
0x9 0x4 0000101 0000000000000000000010000000000000011001 jack m xxschool
但是实际上存储到磁盘里的时候并不是上面这个格式,而是根据数据指定的字符集编码经过编码之后再进行存储
。具体如下:
0x9 0x4 0000101 0000000000000000000010000000000000011001 616161 636320 62626262
同时还会在真实数据之后加入一些隐藏字段
,包括:
-
DB_ROW_ID
:一行记录的唯一标识,如果你没有手动设置主键,那么数据库就会用这个字段作为主键 -
DB_TRX_ID
:事务ID,表示哪个事务更新的这行数据 -
DB_ROLL_PTR
:回滚指针,是在事务回滚时用到的
所以磁盘上最终保存的数据大概是下面这个样子的:
0x9 0x4 0000101 0000000000000000000010000000000000011001 000000000094C 000000000032D EA0000010078E 616161 636320 62626262
这里的000000000094C 000000000032D EA0000010078E 分别是就是DB_ROW_ID,DB_TRX_ID,DB_ROLL_PTR这三个字段。
三、磁盘上的数据页
我们知道每次从磁盘加载到Buffer Pool以及刷回磁盘的时候,都是通过数据页来进行操作的。而数据页中又包含了上面说的一行一行的数据。每个数据页的大小是16KB,除了行数据之外还包含其他一些信息:文件头、数据页头、最大记录和最小记录、空闲区域、数据页目录以及文件尾部。如下图:
这个就是磁盘上一个空的数据页的组织格式,这时候还没有插入数据进来。如果此时我们想要插入一行数据,那么肯定是加载一个空的数据页到Buffer Pool中,然后将这条数据插入Buffer Pool中的空闲缓存页,然后再刷回磁盘,这样一行记录就保存到磁盘中的数据页中了。如下图:
四、表空间和数据区
1. 表空间
表空间就代表着我们创建的某个表所对应的那些数据页,因为一个数据页的大小为16KB,所以不可能一个数据页就是一个磁盘文件
。所以一个表空间的磁盘文件里是有很多数据页的
。但是可能表空间包含的数据页是在太多了,不便于管理,所以MySQL又引入了一个数据区(extent)
的概念。
2. 数据区
一个数据区对应了64个数据页,每个数据页16KB,所以一个数据区是1MB的大小。然后256个数据区又被划分成一组。
对于表空间的第一组数据区的第一个数据区的前3个数据页,都是固定的,存放了一些描述信息。表空间其他组数据区的第一个数据区的前2个数据页也是存放固定的特殊信息的。
所以表空间的实际存储结构大概如下图:
THE END.