mysql的一条记录究竟是怎么存的?

Posted 孙晓凯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql的一条记录究竟是怎么存的?相关的知识,希望对你有一定的参考价值。

数据页

对于Innodb这个引擎来说,它是把数据固化的硬盘的。可以想象一下调用的场景,写一条查询SQL,然后点击回车按钮。这时候这条SQL首先会到达数据库的服务层,在这一层会对这条SQL进行语法分析,语义分析,语句优化。然后会调用数据库引擎(以innodb为例)提供的接口,接口会去机器的硬盘上把数据加载的内存,然后在内存中进行计算(比较,加减等)。得出结果后在返回给客户端(省略了网络部分)。对于插入的操作,也一样,只是先写内存,然后刷到硬盘。
那么此时问题就来了,都知道硬盘的速度和内存的速度相差的非常多。所以这个内存和硬盘之间的io操作肯定是非常浪费性能的操作,所以这种操作能少做就少做,那mysql是咋整的?。mysql采取的策略是把多个记录弄成一页,然后以页为单位进行io操作。这样就减少了io次数,而且多个记录之间在硬盘上是顺序存储的,也提高了硬盘的访问速度。
这个页就是mysql中的数据页,通常来说是16k,数据也里面存的是记录。

行格式

对于每一条记录来说,它会有自己的行格式,这个格式可以在创建数据库的以后指定,比如我司统一的创建数据库的定义为:

CREATE TABLE xxx (
    xxx xxx
    ) CHARSET=xxx ROW_FORMAT=COMPACT;

其中COMPACT就是一种行格式,不同的行格式代表在一条记录行中存储数据的结构不同,重点研究COMPACT格式。

此行格式分为两部分:

  1. 元数据区
  2. 实际数据区。

元数据区又分为三个部分:

  1. 可变长列表
  2. null值列表
  3. 记录头信息

什么是可变长列表?

在一个表中通常有varchar()这样的数据类型。它会根据我们放进去的实际的数据大小进行存贮,我们用起来很省心,很爽。但是机器可必须知道就行存了多大的数据,差一点也不行。所以可变长列表就应运而生,它的作用就是存储每个可变长字段的真实大小。那问题来了,那我应该用多大的数据来存储数据的真实大小啊,变长? 那就成了蛋生鸡鸡生蛋的问题了。
mysql中的算法是这样的:
第一步:计算变长字段最大能存储多大的数据,比如varchar(255),用的是ascii字符集,那么该字段最大就可以存贮255*1=255字节的数据。
第二步:如果一个字段的最大可存字节数小于等于255,那么就可以用一个字节存该字段的实际长度(因为一个字节八位可以表示255),那如果该字段的最大可存字节数大于255呢?一个字节可就存不下了。那就再加一个字节?别忘了,我们现在判断的只是最大可存字节数,并不是实际的字节数,比如即使我定义了varchar(1000),然后我存了一个"a",这时候用两个字节存真实数据显然并不合适。所以如果第一步判断大于255,接下来就需要判断真实数据的大小了。如果真实数据的大小小于等于127,那么就还用一个字节。否则就用两个字节。为啥是127不是255呢,不是一个字节能表示255吗?这就涉及到另一个问题了,就是数据引擎怎么能知道一个字节是单独的表示一个实际数据大小,还是该字节就属于用两个字节能表示一个实际数据大小的其中一个字节(有点绕,实在想不到好的表达方式了)。mysql的做法是用字节的第一位来判断,如果字节的第一位是0,那说明该字节小于等于127,所以就是一个单独的数据。如果一个字节的首位为1,就说明大于127,就表名该字节和该字节之后的一个字节共同代表一个数据。
那如果两个字节也存不下真实数据的大小呢?据说又一个叫做益处区的地方,可以把真实数据截断,存到另一个地方。
经过上面的计算,最终得到可变长字段的真实数据大小,存到记录的开头。

什么是null值列表:

可变长列表目的是不让引擎迷惑,究竟数据有多大。null列表的目的就简单粗暴的多,就是为了节省存储空间。
如果直接在字段上表示一个值是否为null,那么最低需要一个字节,而且需要根据不同的数据类型,不同的字符集来处理。
null值列表的做法就是,用位来标志一个有多少字段位null,每个位0表示为null,1表示不为null,比如条记录中有8个字段为null,那么就用一个字节来表示就OK。那如果有两个字段为空呢?那也是用一个字节来表示,字节是最小单位了,不能在分了。

记录头信息:

记录头信息就是一大串标志位,一共五个字节(固定的),40位,每个位有不用的含义。

实际数据区:

你以为实际数据区就仅仅存储你的业务数据吗?其实并不是,实际数据区里还有一个隐藏区,隐藏区有三个字段:
rowid:如果创建的表中没有主键,没有唯一索引,那么数据库就添加这个隐藏字段,目的是用这个隐藏字段创建聚集索引(innodb引擎来说);
transactionID:待研究
rollpointer:待研究

剩下的才是存储的业务数据[/手动狗头]。

one more thing:char是变长的吗?可变长列表会记录该字段的实际长度吗?
答:这个和字符集有关,如果用的是定长的字符集,那么char就是不可变的,如ascii字符集,么个字符固定用一个字节表示。如果用的是变长的字符集,如utf-8那么该字段就是可变的,因为utf8用1-3个字节表示一个字符。

行格式还有好多,一般业务场景常用的也就这个吧。剩下的有时间再研究。

以上是关于mysql的一条记录究竟是怎么存的?的主要内容,如果未能解决你的问题,请参考以下文章

Mysql究竟是怎么执行的?看完终于不纠结了

操作系统的内存究竟是怎么一回事?

自动玩游戏,手游脚本究竟是怎么做的?

刨根究底字符编码之十二——UTF-8究竟是怎么编码的

刨根究底字符编码之十四——UTF-16究竟是怎么编码的

Nginx|vts模块究竟是什么东西?