《MySQL是怎样运行的》学习笔记(2)——Innodb存储结构

学习资料:
MySQL是怎样运行的:从根儿上理解MySQL

0. 前言

先说一下常见的字符集:

  1. ascii码,范围就是0-127,用一个字节表示
  2. gbk编码,中文,1-2个字节
  3. utf-8,1-3个字节

不同的字符集,其比较规则也不一样。

1. Innodb是如何将表中数据存储到磁盘中的引擎

以页作为磁盘和内存之间交互的基本单位

数据是存在磁盘上的,但是处理数据是在内存中进行的,innodb采用了页这一形式,作为磁盘和内存之间数据交换的基本单位,一页的大小是16kb,也就是说,该存储引擎进行一次数据处理,最少的16kb。

2. 行格式

在innodb引擎,一共有4种行格式

create|alter table xxx row_format=行格式名称

注:
一行数据(所有列)的长度 < 65535个字节。

2.1 Compact格式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqEXH0zr-1572525476848)(evernotecid://0CA62488-0340-4907-9927-B2FF222A28FA/appyinxiangcom/5061692/ENResource/p1677)]

所以说,包括记录的额外信息和真实信息两方面

额外记录信息

  1. 变长字段长度列表
    Mysql将变长字段(如varchar)类型的字段分为两个部分,一个是真实数据内容,另外一个是实际占用的字节数。

  2. null值列表

  3. 记录头信息

    由5个字节,也就是40个二进制位组成,分别代表不同的标志位,大概如下:
    在这里插入图片描述

记录真实数据

  1. 三个隐藏列
    事务id(transaction_id)
    回滚id(roll_pointer)
    row_id,不一定会有,(无主键,无unique列时)
  2. 数据列

注:
当定长字段的字符集是变长的时候(例如utf-8,gbk)其字段长度也会加入变长字段长度列表;
varchar(M)的字段,代表其最多能存储M个字符,字符集最多需要使用的字节数为W,所以代表这个字段最多能占用的字节数为 = M * W;

2.2 Redundant格式

在这里插入图片描述

额外信息

  1. 字段长度偏移列表
    记录的不是字段所占的实际字节数,而是从第一个字段开始,每个字段的偏移量,比如一行中,第一列的地址为01,第二列占4个字节,那么第二列的偏移量就是(01 + 4)= 05。
    该长度信息是用一个字节存储,还是用2个字节存储,要看记录的真实数据占用的总大小来决定的。当不大于127时,就用1个字节,当大于127,小于32767时,用两个字节。

  2. 记录头信息

注:
一个字节是8位,也就是256,为啥用127来作为分界点呢?看起来,正好差了8位的最高一位,最高一位,嗯,这么说是不是有点感觉了,对的,标志位。因为Redundant格式没有专门的null值记录,这个最高位就是用来标记该字段是不是null值的。

真实数据

和Compact一样

2.3 行溢出

上面提了一句,一行的真实数据不要超过65535,但其实很好超过的,那么怎么办?

那就和链表一样,分开存储,innodb以页位单位,存储到别的页上面。

在Compact和Redundant格式中,是存储一部分数据(768字节),然后花20字节存储指向溢出页的地址。

在这里插入图片描述

但是在Dynamic和Compress格式中,就不存储数据,直接存储溢出页的地址。

在mysql 5.7中,默认行格式就是Dynamic。

3. 数据页(索引页)结构

上面说了,innodb是以页作为磁盘和内存中间数据交换的基本单位,每个页16kb,其实为了不同的目的,innodb设计了很多种不同的页,当然,最基本的还是存实际数据的页。其结构如下:

在这里插入图片描述

先说说User Records

顾名思义,这就是我们存记录的地方。

其实在最开始没有存记录时,是没有这个地方的,只有Free Space,就是空的地方,在存入记录时,就从free space开辟空间。

前面说了行格式,在每行记录的头信息中,有一位为 next_record,指向下一条记录的真实数据地址处,就是用这个形式,将一行行记录连接成一个单链表形式。

但是有一个问题,在插入第一条记录的时候,是从哪指过来的呢?嗯,这就是Infimum的作用了,这是默认的最小行,可以看成头结点。同理,supremum最大行就是单链表的尾结点,这条链表是以主键按照从小到大的顺序连起来的。

最大行和最小行的概念,可以简单理解为,按照主键默认从小到大排序的大小。前面说了,如果没有主键(或unique列),会自动产生一个row_id列,就可以简单的理解为以row_id的大小比较。

既然为单链表,那么插入和删除就好办了,本来默认是

infimum -> p -> supremum

在记录p后面插入一条记录s的话,只要将其中next_record的指针改一下就行了。

infimum -> p -> s -> supremum

删除同理,移动链表即可,但是在头信息中有一个标注 delete_mask,标记是否删除。实际上,在逻辑上删除一行,实际物理存储并没有删除,至少没有马上删除,可以留给后面的插入记录进行空间覆盖,或者删除的记录连起来形成一个垃圾链表。

其实mysql中规定了,一页中最少要有2条数据记录,在空着的时候,有infimum + supremum两条,在不断插入的过程中,不断占用free space 的地盘,在记录头信息中的heap_no可以用来标记当前记录在当前堆的位置,但是从2开始的,因为前面的 0 和 1 分别为infimum和supremum。

刚才说了插入和删除,那么查找呢?这就要用到数据结构中的分块索引了,本来一页的数据就不多,但是为了提高查找效率(个人说法),在页内还采用了分块索引。这个关键码存储的地方就是Page Direction,也挺形象的,就是这一页的目录。分块索引再说一下:

  1. 块内最大关键码:就是将数据记录分组后,每个组内的最后一条记录,也就是最大的那条(因为已经按主键排序),其地址偏移量;
  2. 块中记录个数:就每条记录头信息中,有个n_owned属性,就是该记录有多少条记录,就是该块内(组内)有多少记录;
  3. 用于指向块首元素的指针:就是最大关键码对应的next_record属性嘛;

当然,在mysql中不叫块,叫slot(槽)。这样就可以使用二分查找了。

再复习一下分块索引的特点:
块间有序,块内无序。不过由于记录原先就是按照主键排序的,所以在这里面块内也是有序的。

上面说了一下各行记录之间的连接(单链表)和增删查,还有一个问题是:页是内存和磁盘之间数据交换的基本单位,内存,就涉及到一个掉电就丢失的问题,所以存在一个需要校验的过程。由于页是从头开始读的,在处理完毕,开始同步的时候,File Header中算出来一个校验和,,到File Tail的时候,再算出来一个校验和,看是否一致,如果没有意外应该是一致的,二者不同就意味着同步出了问题。

再放一个图:

在这里插入图片描述

好了,从上往下捋一下:

  1. File Head,38个字节,表示页的一些基本信息,比如上一页,下一页的双指针链表;
  2. Page Head,56个字节,本数据页中存储记录的状态信息,比如本页中已经存储了多少信息,第一条记录的地址是多少,Page Direction中存了多少slot等;
  3. Infimum + supremum,26个字节,这是最小行和最大行,我个人感觉就相当于单链表的头和尾;
  4. User Records:大小不固定,真实存储插入的数据记录;
  5. Free Space:大小不固定,页中尚未使用的部分;
  6. Page Direction:大小不固定,分块索引的关键码索引,插入的记录越多,占用空间越多;
  7. File Tailer:8个字节,检验页是否完整的部分;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值