数据是如何存储在磁盘的

表空间

  我们脑子里理解数据的存储就是‘一个库里面有一些表,表里面有很多字段,然后有很多行数据’,但其实这只是我们的逻辑概念,数据在磁盘物理存储方式可不是这样的。mysql在服务器上是以一个文件夹的形式出现的,你创建了test数据库,那么在磁盘上就会出现test的目录,而创建的那些表,其实都是有一个表空间的概念,在磁盘都会对应一个表名.ibd数据文件。

  

extent

  表空间的磁盘文件里有很多个数据页,为了管理,表空间里又引入了一个数据区(extent)的概念。每个数据区大小是1mb,一个数据页只有16kb、所以数据区对应着64个连续数据页,然后256个数据区被划分为一组。对于表空间而言,他的每个数据组的第一个数据区的前3个数据页,都是固定的,存放表空间和数据页相关描述信息的其他数据区的前两个数据页,也是放一些相关属性描述的。

  

数据页

  在研究 buffer pool 的时候我们知道是以数据页为单位加载数据的,同样也是以页为单位将数据刷盘到磁盘的。 那这个数据页到底长什么样子呢?它其实拆分成了很多个部分,大体来说包含了文件头、数据页头、最小记录和最大记录、多个数据行、空闲空间、数据页目录、文件尾部。
  文件头占据了38个字节,数据页头占据了56个字节,最大记录和最小记录占据了26个字节,数据行区域的大小是不固定的,空闲区域的大小也是不固定的,数据页目录的大小也是不固定的,然后文件尾部占据8个字节。

  

一行数据是如何存储的

行格式 

  对于数据页中的每一行数据存储,这里涉及到"行格式"的概念,就是我们可以对一个表指定它的行存储的格式是什么样,既可以在建表时候指定、也可以后续修改存储格式,这里我以COMPACT为例,除了这个还有其他集中存储的格式,基本都大同小异。那比如我现在这里有一张表,五个字段分别为name、address、genderjob、school,就代表了客户的姓名、地址、性别、工作以及学校。

CREATE TABLE customer (
    name VARCHAR(10) NOT NULL,
    address VARCHAR(20),
    gender CHAR(1),
    job VARCHAR(30),
    school VARCHAR(50)
) ROW_FORMAT=COMPACT;

  compact的存储格式大概分为4部分 变长字段的长度列表,null值列表,数据头,具体每一列数据。现在有这么一行数据“jack NULL m NULL xx_school”,它的存储格式大概长下面这样,接下来我会逐步分析每个字段的具体含义。

  0x09 0x04 00000101 0000000000000000000010000000000000011001 616161 636320 6262626262

变长字段列表

   mysql中有些字段长度是固定的,有些长度是不固定的,比如varchar这种不固定的,就叫变长字段。比如现在表里的五个字段值为 hello hi b word a 那么此时你要读取第一个字段的值,那么第一个字段是变长的,到底他的实际长度是多少呢?此时你会发现第一行数据的开头有一个变长字段的长度列表,里面会读取到一个0x05这个十六进制的数字,发现第一个变长字段的长度是5,于是按照长度为5,读取出来第一个字段的值,就是“hello”,同样的后面分别为 0x02 0x04 0x01 就取出了 hi word a ,至于长度固定了的char(1)就直接取出来了 b 。只是变长字段列表是逆序存储的!也就是 0x02 0x04 0x01 null值列表 数据头  hello hi b word a

null值列表

  null值列表,顾名思义,说的就是你一行数据里可能有的字段值是null,比如你有一个name字段且允许为null的,那么实际上在存储的时候,如果你没给他赋值,他这个字段的值就是null。比如现在表中一行记录为“jack NULL m NULL xx_school”,他的5个字段里有两个字段都是NULL。我们知道对于变长字段会记录在变长字段列表里面,但是这里要区分一下,那就是如果这个变长字段的值是NULL,就不用在变长字段长度列表里 存放他的值长度了,所以在上面那行数据中,只有name和school两个变长字段是有值的,把他们的长度按照逆序放在变长字段长度列表中就可以了,如下所示:0x09 0x04 NULL值列表 头信息 column1=value1 column2=value2 ... columnN=valueN

   null值在磁盘上,是通过二进制的bit位来存储的,如果bit值是1说明是NULL,如果bit值是0说明不是NULL。比如上面4个字段都允许为NULL,每个人都会有一个bit位,这一行数据的值是“jack NULL m NULL xx_school”,所以4个bit位应该是:1010;但实际他是按逆序放的,所以是:0101。而null值列表一般起码是8个bit位的倍数,如果不足8个bit位就高位补0,所以实际存放看起来是如下的:0x09 0x04 00000101 数据头 column1=value1 column2=value2 ... columnN=valueN

数据头

数据头是用来描述这行数据的,它只有40个bit位大小。
  前两个bit位都是预留位,没有任何含义
  第三个是delete_mask,用来标记这行数据是否被删除了
  第四个是min_rec_mask,他其实就是说在B+树里每一层的非叶子节点里的最小值都有这个标记
  接下来有4个bit位是n_owned,他其实就是记录了一个记录数
  接着有13个bit位是heap_no,他代表的是当前这行数据在记录堆里的位置
  然后是3个bit位的record_type,这就是说这行数据的类型:0代表的是普通类型,1代表的是B+树非叶子节点,2代表的是最小值数据,3代表的是最大值数据
  最后是16个bit的next_record,这个是指向他下一条数据的指针
那么他真实存储大致如下所示:
0x09 0x04 00000101 0000000000000000000010000000000000011001 jack m xx_school

数据值

  我们已经知道他的变长字段的长度,用十六进制来存储,然后是NULL值列表,指出了谁是NULL,接着是40个bit位的数据头,然后是真实的数据值,就放在后面。在读取这个数据的时候,他会根据变长字段的长度,先读取出来jack这个值,因为他的长度是4,就读取4个长度的数据,jack就出来了;然后发现第二个字段是NULL,就不用读取了;第三个字段是定长字段,直接读取1个字符就可以了,就是m这个值;第四个字段是NULL,不用读取了;第五个字段是变长字段长度是9,读取出来xx_school就可以了。
但实际上我们的数据都是进行编码之后再存储的,会在他真实数据部分加入一些隐藏字段:
  首先有一个DB_ROW_ID字段,这就是一个行的唯一标识,是他数据库内部给你搞的一个标识,不是你的主键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) 616161 636320 6262626262

行溢出

  我们说过一个数据页是16kb,那如果一条数据不止16kb呢?比如 varchar(65532),或者text、blob 这种字段、是很可能溢出的,然后数据就会存储在多个数据页里。所以在存储的数据中,会有一个指针将这些数据页串联起来。

  

原文地址:https://www.cnblogs.com/wlwl/p/14412233.html