提示:文章内容来自《mysql是怎样运行的》以及部分B站宋红康老师的视频,这里仅仅是我的笔记,对重点内容的记录。强烈推荐购买这本书《mysql是怎样运行的》。


文章目录

  • 前言
  • 一、InnoDB行格式
  • 1.COMPACT:
  • 2.REDUNDANT
  • 3.对比总结
  • 二、 页
  • 2.1 页的结构
  • 三、 总结



前言

前面对mysql的逻辑架构做了一个介绍,也介绍了一下一条sql的执行流程。之前说过,mysql的系列的重点还是在索引、事务、锁上。在介绍索引之前,首先对innodb的行格式和页做个介绍,页是mysql内存和磁盘交互的基本单位,一个页默认是16kb,为什么按页进行交互(也就是读写)?主要还是为了减少IO的次数。那么行格式又是什么,InnoDB的行格式有很多种,行格式其实就是页中每条记录存储的格式。

一、InnoDB行格式

InnoDB是一个将表中的数据存储在磁盘上的存储引擎,即使我关闭或者重启服务器,数据依然存在。而真正处理数据的过程发生在内存,所以需要把磁盘中的数据写到磁盘中,或者把磁盘中数据读取到内存中,而又不能一条条的处理,这样效率太慢了,因此InnoDB采取的方式是将数据划分为若干个页,每次按页读取或写入数据,因此说页是磁盘和内存交互的基本单位,页默认大小是16kb
InnoDB的行格式有四种:COMPACT、REDUDENT、DYNAMIC、COMPRESSED
我们的重点在COMPACT、REDUDENT

1.COMPACT:

innodb索引数据结构 mysql innodb的索引与数据存储格式_主键


1.1、记录的额外信息

1)变长字段长度列表

mysql支持一些变长的数据类型,比如VARCHAR、VARBINARY(M)、各种TEXT类型、各种BLOB类型。我们也可把拥有这些数据类型的列称为变长字段。变长字段存储多少个字节是不固定的,所以我们需要在存储真实数据的时候,把这些数据占用的字节数也存储起来,这样才不至于把mysql搞懵,也就是说这些变长字段在存储的时候包括两部分:

真正的数据内容
该数据占用的字节数

注意:
1、各变长字段的真是数据占用的字节数按照列的逆序存放
2、如果表中的列都不是变成的的数据类型,或者所有列的值都是NULL的话,就不需要有变长字段长度列表了
3、对于char(M)列的存储格式:
当采用的是定长编码的字符集时,比如ascii,该列占用的字节数不会被加到变长字段长度列表中;如果采用的是变长的字符集时,比如gbk/utf-8,该列占用的字节数就会被加载到变长字段的长度列表中
2)NULL值列表
如果把NULL值都放在记录的真实数据中存储会很占地方,所以COMPACT把NULL值进行了统一管理:
首先统计表中允许存储NULL的列有哪些
如果表中没有允许存储NULL的列,则NULL值列表就不存在了
NULL值列表也是逆序存放的
3)记录头信息
预留位1:没有使用
预留位2:没有使用
Delete_tag:标记改行数据是否被删除,如果为1,说明被删除了,如果为0说明没被删除
这里提一下为什么通过标记来判断是否删除了某行数据
因为在移除数据的时候,还要重新排列其他记录,但是打一个标记就可以解决这个问题。所有被删除的记录会形成一个垃圾链表,被称为可重用空间,后面有新纪录插入的时候,会覆盖它
min_rec_flag:每层非页子节点的最小目录项会添加该记录
n_owned:每个页都会按照槽形成目录项,每个槽中都有一个最大的,这个参数在最大的中,会记录下该槽中真实数据的个数
heap_no:记录当前页在堆中的相对位置,0表示页中的最小记录,1表示最大记录
record_type:0表示普通记录,1表示B+树非叶子节点的记录,2表示最小记录,3表示最大记录
next_record:表示下一个记录的相对位置
1.2、记录的真实数据
记录的真实数据除了我们自己的列的数据外,MYSQL会为每个记录默认地添加一些列(也称为隐藏列)
row_id :行id ,唯一标识一条记录
trx_id:事务id
roll_point:回滚指针

根据row_id延申出主键的生成策略
我们说InnoDB存储引擎一定存在聚簇索引,也就是说一定存在主键,但是这个聚簇索引或者说主键是怎么来的呢?
如果我们定义了主键,那么InnoDB就会按照我们定义的列生成聚簇索引
如果我们没有定义主键,但是表中存在非空且唯一的列,那么就会以此列作为主键生成聚簇索引
如果以上都不存在,那么row_id就派上用场了,InnoDB就会以row_id作为主键生成聚簇索引

讲一下我对这块的理解,为什么它一定要有个主键的生成策略,首先,以InnoDB这个存储引擎存储的表,它完整的数据是存储在聚簇索引的叶子节点上的,非聚簇索引无论是它的内节点还是叶子节点中,也保存了主键,一是保证内节点内部数据的唯一性,二是通过主键进行回表。因此对于InnoDB来说,主键必不可少!

2.REDUNDANT

innodb索引数据结构 mysql innodb的索引与数据存储格式_innodb索引数据结构 mysql_02


2.1 字段长度偏移列表

没有了“变长” REDUNDANT的行格式会把该记录中所有的列(包括隐藏列)的长度信息按照逆序存储到字段长度偏移列表中

多了个“偏移”意味着计算列值长度的方式不像COMPACT行格式那样直观,它是采用两个相邻的偏移量来计算各个列值的长度

2.2 记录头信息

预留位1:没有使用

**预留位2:**没有使用

Delete_tag:标记改行数据是否被删除,如果为1,说明被删除了,如果为0说明没被删除。

min_rec_flag:每层非页子节点的最小目录项会添加该记录

n_owned:每个页都会按照槽形成目录项,每个槽中都有一个最大的,这个参数在最大的中,会记录下该槽中真实数据的个数

heap_no:记录当前页在堆中的相对位置,0表示页中的最小记录,1表示最大记录

n_field: 表示记录中页的数量

1 byte_offs_flag: 标记字段长度偏移列表中,每个字段使用的偏移量是使用一个字节还是两个字节

next_record:表示下一个记录的相对位置

3.对比总结

1、首先COMPACT行格式的开头是【可变字段长度列表】而REDUNDANT行格式的开头是【字段长度的偏移列表】
2、对NULL值的处理上,COMPACT有【NULL值列表】,在其中记录NULL的列,REDUNDANT在字段长度偏移链表中对各列的偏移量做了特殊出里——将对应偏移量值的第一个比特位作为是否为NULL的依据,1为NULL
3、记录头信息
COMPACT有record_type
REDUDENT有 n_field和1 byte_offs_flag

4、对溢出列的处理:

对于COMPACT和REDUNDANT,如果每一列的数据非常多,则在记录的真实数据只会存储该列的前768字节的数据以及一个指向其他页面的地址,把剩下的部分放下其他页中,这些存储768字节之外的数据的页面也称为溢出页。

innodb索引数据结构 mysql innodb的索引与数据存储格式_主键_03


DYNAMIC和COMPRESSED,它俩是把所有的真实数据都存储到溢出页,而在真实数据处存储指向溢出页的地址

innodb索引数据结构 mysql innodb的索引与数据存储格式_主键_04

二、 页

首先页作为mysql内存和磁盘交互的基本单位。页的默认大小是16kb

2.1 页的结构

innodb索引数据结构 mysql innodb的索引与数据存储格式_数据_05


File Header:记录页的通用信息,只要是这其中有一个参数叫做“校验和”,同时页与页之间的双向链表也是通过这里的FIL_PAGE_PREV指向前一个页,FIL_PAGE_NEXT指向下一个页。

Page Header:记录通用信息。

Infimum+Supremum:页中的最小记录和最大记录,这两个是mysql默认创建的。

User Records:用户使用的列表,就是存放数据的地方。

Free Space:空闲列表。

Page Directory:页目录,它的作用就是通过对页中的数据,按照槽进行分组,利用二分查找的思想,加快对页内数据的检索。

File Trailer:这里的前4个字节代表页的校验和,这部分与File Header中的校验和对应。如果在同步的过程中,系统出现问题,就可能导致磁盘中的页数据没能完全同步,也就是发生了脏页的情况。为了避免发生这种问题,mysql在每个页的尾部加上了File Trailer来校验页的完整性。

File Trailer由8个字节组成,前4个字节代表页的校验和:这个部分是和File Header中的校验和相对应的。简单理解,就是File Header和File Trailer都有校验和,如果两者一致则表示数据页是完整的。否则,则表示数据页是脏页。Mysql会有专门处理脏页的方式。

这些参数目前看不懂没关系,后面会对涉及到的进行介绍,也没必要全部记住,知道解决什么问题的就行了。


三、 总结

通过对行格式和页结构的分析,我们可以得出一个结论:
各个数据页可以组成一个双向链表,而每个数据页的记录会按照主键值从小到大顺序组成一个单向列表,每个数据页都会为存储在它里里面的记录生成一个目录项,在通过主键查找某个记录的时候,可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽中的记录即可快速查找到指定的记录。页中的文件头和文件尾都有一个检验和,主要是判断我们的页是否是脏页。

补充:
删除一条数据(这里针对的是事务操作,事务操作会麻烦一些,后面继续补充):
1、该记录的delete_tag改为1
2、该记录的的next_record变为0,它上一条记录的next_record跳过被删除的记录,指向被删除记录的下一条
3、还有就是该记录所在槽的“大头大哥”的n_owned减1。

innodb索引数据结构 mysql innodb的索引与数据存储格式_innodb索引数据结构 mysql_06