MySql数据在磁盘上究竟是怎么存储的?被存储的数据怎么查找?

本文来自做者投稿,原做者:zyz1992mysql

关于MySql数据库,相信不少人都不陌生,这是当今最经常使用的一种关系型数据库,关于MySql的知识也是很丰富的。sql

那么,不知道你们有没有想过这样的问题:MySql中的数据是存在哪的?又是如何存储的呢?数据库

本文就来深刻分析一下这些问题。文章内容很长,建议收藏,建议你们静下心来仔细阅读,必定会有收获!缓存


 

Innodb的存储格式

咱们知道,关于Mysql这种关系型数据库,里面保存的数据最终都是要持久化到磁盘文件上面的。磁盘文件里存放的物理格式就是数据页(关于数据页,若是不太理解先忽略,后续文章单独介绍),数据页中存放的是一行一行的记录,可是对于数据页中的每一行数据他又是怎么存储的呢?学习

咱们拿Mysql中最经常使用的Innodb引擎来重点说,介绍下存储格式是怎样的。编码

MySQL中存储有3种:

server层格式:与存储引擎无关,Binlog存储经常使用的一种 (Bin Log 咱们前面已经详细介绍过了,这个是MySql主从复制的一个很重要的文件)设计

索引元组格式:InnoDB存取过程记录的中间状态,是InnoDB在内存中存储的格式 (换句话说咱们的增删改的操做都是在内存中执行的,这个只是一种临时状态)3d

物理存储格式:记录在物理页面中的存储格式,即compact格式,与索引元组格式一一对应。(这个是数据在磁盘存储的真正的格式)指针

MySql 的 InnoDB 存储引擎和大多数数据库同样,都是以行的形式存储数据的,咱们能够经过SHOW TABLE STATUS查看到行的的存储格式。orm

InnoDB 储存引擎支持有四种行储存格式:COMPACT、Redundant、Dynamic 和 COMPRESSED。默认为COMPACT。


 

其余的参数咱们这里不关注,仅仅看 Row_format 这列,这里咱们能够看到行的存储格式是 Compact,Compact 存储数据的格式大体以下这样


 

对于咱们看到的每一行数据,咱们最早看到的好像并非各个列,而是一些相似列的描述信息。没错,其实在存储的时候都会有一些都字段来描述这一行的信息,这就比如缓存池中的描述缓存页的描述数据相似。

上面的图片你们能够这么简化来对待,事务ID和回滚指针你们先不要关注,省得由于这个产生干扰而难于理解


 

一、变长字段 varchar 是如何存储的

通常状况下,咱们要存储的数据是并不能肯定他的长度的,大部分状况下都是一些变长的数据,以varchar为例,假设如今三个字段,字段类型分别为:varchar(10),char(1),char(1),char你们都是知道的,存储的基本是一些已知的长度固定的数据,假设这三个类型的字段分别有以下的数据:

第一行:mysql a a;第二行:dog b c;画个图来帮助你们想象,如今你看到的是数据中为咱们展示的样子。


 

可是在磁盘中可不是这样子的,前文已经提到过,表空间和行这些实际上是逻辑上的概念,而数据页是一种物理概念,也就是说咱们看到的样子在磁盘中的样子本本是不同的。

在磁盘中这两条记录大体是这样子的:mysql a a dog b c,他们在磁盘中都是挨在一块儿存储的。

是否是瞬间感受想要去查找一条数据很是麻烦,告诉你:是的,因此 MySql在设计的时候才会使用行格式存储,才会有前面的哪些变长字段列表和标志位以及记录信息,这些就是用来记录一行的记录的信息,换句话说,MySql是经过这些描述信息来定位到一行中的具体记录的。

以第一行记录为例,它在磁盘中的记录状况大体是下面这样子的,首先咱们须要明确知道的是各个字段的类型MySql是很清楚的,在这个基础上咱们能看明白下面和想通后面的事情。首先咱们看到 mysql是5个字符,使用十六进制表示是 0x05,因此他的存储大概是这样子的:


 

同理第二行数据相似这样子的:


 

相信你们在看到这里已经大概能推测出MySql这个时候是怎么读读取数据的了,就是他会先根据变长字段长度列表中描述的变长字段的信息去查找变长字段,例如第一行,MySql解析到变长字段是5,因此他会在mysql a a dog b c 这些里面取出5个字符,也就是 mysql,紧接着后面是两个 char(1) 也就是两个 a 在依次取出来。

中间设备。由浅入深,咱们慢慢来,刚刚上面说到的仅仅是一种很是简单的状况,这个首先是帮助你们理解,让你们先明白有这么个回事,是这么回事,而后在慢慢的挖掘,咱们必定要一个萝卜一个坑的去踏实学习

如今若是是多个varchar类型的字段怎么办?例如:varchar(3),varchar(10),varchar(4),char(1),他有一条记录是这样子的:aaa,bb,cccc,d,你根据上面的能推测出磁盘中的行记录是怎么样子的吗?

你是否是这么想的:磁盘中确定是这样的:0x03,0x02,0x04 null标志位 记录头信息aaa bb cccc d;这么想的同窗请鼻子靠墙:);实际上并非这样子的。

当有多个变长字段的时候,MySql在 compact 行格式中,把全部变长类型的长度存放在行记录的开头部位造成一个列表(这个列表就是刚刚上面说的变长字段列表),按照列的逆序存放,也就是大体是这样子的:


 

这里我必需要给你们解释下变长字段列表会逆序存放,由于每行记录的都有一个next_record指针 指向下一行 记录头信息和 真实数据 之间的位置。由于这个位置刚恰好,向左读取就是行描述相关信息,向右读取就是真实数据。正好对应变长字段长度列表。画个图来帮助你们理解下:


 

说到这里咱们来稍微小结一下

MySql中数据在磁盘的存储小结

数据在磁盘中的存储在物理空间上面是连续的

数据是被存放在MySql设计出来的数据页上面的,数据页上面存储的才是最终的一行一行的记录

行的存储格式默认是Compact

每一行数据都会有相应的行描述部分,描述部分有【变长字段列表】【NULL标志位】【记录头信息】

每一行都会有next_record指针,指向记录头和变长字段列表的中间某个位置,方便寻址

变长列表中的varchar列的描述是逆序的(和字段的顺序相反)这样作的目的在上图中描述的很清楚了


 

二、NULL字段是如何存储的

上面说到了状况都是比较正常的状况,也就说上面提到的字段是没有空值的,无论是变长字段仍是char字段,都是有值的,那若是某个字段容许为空,且值确实为空,MySql又是怎么处理的呢?是否是直接存储NULL呢。

假设MySql针对与Null直接存储,他其实是按照“NULL”这样字符串的形式存储的,这样显然不行啊,由于字符串要占用空间的啊(一个 NULL 字符串要占用四个字符呢),你都没有值,还占这么多空间,因此MySql确定不是这样存储的。其实MySql在处理NULL值的时候是会将它通二进制来存储的,且也是逆序的

MySql是如何经过二进制来存储NULL值的?

上面的 Compact 格式数据中的【NULL标志位(也能够叫NULL列表)】就是用来存储NULL值的。如有某个字段值为 null,将将其 bit 位置为 1 说明值为 NULL,bit为 0 说明该字段值不为空

是否是听了解释仍是稀里糊涂的,别急,我画个图再来详细介绍下,先假设咱们有一张 sutdents 表

CREATETABLE`students`(

`name`varchar(10)NOTNULL,

`address`varchar(255)DEFAULTNULL,

`gender`char(1)DEFAULTNULL,

`class`varchar(10)DEFAULTNULL,

`hobbies`varchar(255)DEFAULTNULL,

PRIMARYKEY(`name`)

)

他有这样一行记录


 

咱们先看变长字段列表部分(记住是逆序存放的):

roles是长度为5记做:0x05;address 为null,不放在变长列表中、gender 是 char 类型,不放在变长列表中、class为空,不放在变长列表中、hobby_xx长度为8记做:0x08;因此变长列表的记录为:0x08 0x05

如今到了NULL标志位了:依旧是从右往左记录字段:name 在设计的时候就是 not null,所示是不会出如今NULL标志为中(Null标志为是用来记录字段可为NULL的字段,字段不能够为NULL的不是会被记录到NULL标志位的),address为NULL记做1,gender不为null记做0,class 为null记做1,hobbies不为null记做0;因此按照字段的顺序结果就是:0101,可是NULL标志位是逆序的,因此NULL标志位存放的结果大概是这样子的:0101,高位补0便可


 

咱们来模拟读取下这条记录:MySql 对于字段的类型必定是已知的(这个是在建立数据表的时候就已经定下来了),因此对于 name 这种 not nul l的字段是不会去存放在null标志位的,下面是详细的读取步骤:

name字段是主键,不可能在NULL 标志位中的,又由于 name 是varchar 字段,因此就会去变长字段列中查找,找到值为 0x05 接着就会去字段列表中读取5个字符的长度,也就是 roles ,第一个字段读取成功;

接着是 address 字段,由于类型是 MySql 已知的,又由于字段值为 null 因此就不须要去读取了,第二个字段读取结束;

接着是gender字段,是char类型的,直接拿到 f 就能够了;

下一个是class 字段,由于是null 因此根本不会去变长字段中查找;

最后一个是 hobbies 字段,由于不为null ,又是第二个变长字段,这个时候就会去 变长字段列表中查找,结果定位到是 0x08 那就读取 8 个字符的长度出来,拿出来是hobby_xx;

说到这里,关于一行记录的中的变长字段列表和 NULL 标志位具体是如何读取字段值的就给你们介绍完了,不知道你们看到以上内容脑子是否是会展示一条条行记录的描述信息。目前咱们只须要了解 varchar 和 NULL 存储的基本就足够了,由于这两个表特殊,也是最常用的,其余的字段类型本篇暂且不展开讨论了。

上面的记录头的信息咱们尚未讨论过,下面咱们再详细介绍下记录头信息是什么。

记录头信息

记录头信息由40位的bit位组成,其各个位的划分和含义以下:


 

记录头的各个位的做用其实就已经说的很清楚了,一些概念如今还无法讲解,不少东西须要到索引的时候才能展开讲,这里你们须要明确的就是各个标志位的含义。

我认为对于记录头的了解到这里就足够了,各个标志位的含义明确了到这个程度就好了,至于更多的可能咱们根本接触不到。这一小节就当是科普。


 

三、数据在磁盘上究竟是怎么存储的

上面画过这样一张图:


 

以前说的是数据大体是这样子在磁盘中存储的:0x03 NULL标志位 记录头信息 dog b c,可是实际上后面的列的数据并是否是咱们看到的这个样子,磁盘在存储的时候是根据数据库指定的字符集编码存储起来的你觉得多是上面那样子存储的。

实际上多是在样子的:0x03 NULL 标志位 记录头信息 1233 323 223,也就是说实际的数据在磁盘上存储根本不是咱们人能认识的,后面的 1233 323 223 这几个是我乱写的,没什么含义,主要是想代表是计算在实际存储的时候是以特定的字符编码来存储的。

另外每一行数据在被存储的时候实际上还会有隐藏的字段,相信你们对这个应该不会陌生的,row_id 你们应该是知道的,哪怕本身没用过可能也是听过的,这个是数据库本身为咱们的每一行记录生成的一个惟一的表示,若是咱们没有为数据表指定主键字段,也没有指定 Unique key,那么这个时候数据库内部会帮咱们维护一个自增加的 ROW_ID 字段做为主键。

还有一个隐藏字段就是 事务ID 上面的第二张图上层画出来过,这个顾名思义了,就是和事务相关的一个字段属性字段名为DB_TRX_ID,这个再详解到事务的时候再详细介绍;最后一个也是在上面的第二张图上画出来了,就是回滚指针 DB_ROLL_PTR,回滚也是事务使用到的概念,也是放在事务那边跟你们介绍

如今再来总体回顾下一行记录在磁盘中的存储的结构大概是什么样子的:

0x08 0x05 00000101 000001010000000000000000000000000000001021134 44 232343

说到了存储,咱们顺便聊聊和存储相关的一个概念,行溢出。

行溢出

说到这里,不知道你们有没有想过一个问题,就是咱们一直在说 MySql 存储是以数据页的形式来存储的,而后数据页中记录的是一行行的记录,可是每每常规状况下不会有什么问题。

可是若是如今有一行记录很是大,由于数据页大小默认也就是16KB,假设某张表里面有text字段也有BLOB字段,且这一行的记录的大小远远超过了一个数据页的大小16KB,这种状况称之为行溢出。

MySql 是怎么来处理这种行溢出的状况的呢?实际上很简单,一个数据页不够就使用多个数据页,数据页和数据页之间使用链表连起来,之因此可以使用链表链接由于数据页里面是包含了存放指针的 bit 位。对于行溢出的概念了解到这个程度就足够了。咱们学习是有的放矢,不是什么都要去刨根问底的。


 

结束语

本片文章详细的介绍了 MySql 存储数据的格式和数据具体在磁盘中是怎么存储的,被存储的数据又是怎么查找的,说白了不少事情都是已是既定的规则,所谓既定的规则就是很对东西已经被更早的设计出来。

因此你在使用和了解的使用只须要按照被人的规则来执行,而后在此基础上深刻了解下别人为何这么设计?这样会更有助于咱们掌握和理解某个知识点。

文章来源:https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650152173&idx=1&sn=649e69f288d3d529d3af5282584b97dc&chksm=f36801ccc41f88dae42bf2914ae341aca27ee1284d06b50e8801e261bcb20e0c8cc380194edc&scene=21#wechat_redirect

相关文章
相关标签/搜索