MySQL是怎样运行的——笔记一

InnoDB是一个将表中的数据存储到磁盘上的存储引擎,所以即使关机后重启我们的数据还是存在的。而真正处理数据的过程是发生在内存中的,所以需要把磁盘中的数据加载到内存中,如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。读写磁盘的速度非常慢,和内存读写差了几个数量级,所以想从表中获取某些记录时,InnoDB存储引擎需要一条一条的把记录从磁盘上读出来么?不,那样很慢,InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。

InnoDB记录存储结构

平时是以记录为单位来向表中插入数据的,这些记录在磁盘上的存放方式也被称为行格式或者记录格式。设计InnoDB存储引擎的大佬们到现在为止设计了4种不同类型的行格式,分别是CompactRedundantDynamicCompressed行格式,随着时间的推移,他们可能会设计出更多的行格式,但是不管怎么变,在原理上大体都是相同的,下面开始介绍:

compact行格式

读取方式:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。

一条完整的记录其实可以被分为记录的额外信息记录的真实数据两大部分。

记录的额外信息

这部分信息是服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为3类,分别是变长字段长度列表NULL值列表记录头信息

1.变长字段列表

Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序逆序存放,再次强调一遍,是逆序存放!

2.NULL值列表

我们知道表中的某些列可能存储NULL值,如果把这些NULL值都放到记录的真实数据中存储会很占地方,所以Compact行格式把这些值为NULL的列统一管理起来,存储到NULL值列表中,它的处理过程是这样的:

  1. 首先统计表中允许存储NULL的列有哪些。
  2. 如果表中没有允许存储 NULL 的列,则 NULL值列表 也不存在了,否则将每个允许存储NULL的列对应一个二进制位,二进制位按照列的顺序逆序排列,二进制位表示的意义如图1-1
  3. MySQL规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0,只有3个值允许为NULL的列,对应3个二进制位,不足一个字节,所以在字节的高位补0图1-2。如果一个表中有9个允许为NULL,那这个记录的NULL值列表部分就需要2个字节来表示了。
1-1
1-2

c1c3c4这3个列中c3c4的值都为NULL,所以这3个列对应的二进制位的情况就是:所以第二条记录的NULL值列表用十六进制表示就是:0x06。所以这两条记录在填充了NULL值列表后的示意图就是这样:

3.记录头信息

它是由固定的5个字节组成。5个字节也就是40个二进制位,不同的位代表不同的意思,具体信息暂略(P5)。如图:

4.记录的真实数据

记录的真实数据除了c1c2c3c4这几个我们自己定义的列的数据以外,MySQL会为每个记录默认的添加一些列(也称为隐藏列

  • row_id 6字节行ID,唯一标识一条记录(DB_ROW_ID,非必须)
  • transaction_id 6字节事务ID(DB_TRX_ID,必须)
  • roll_pointer 7字节回滚指针(DB_ROLL_PTR,必须)

InnoDB表对主键的生成策略:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键,如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。

所以得出:InnoDB存储引擎会为每条记录都添加 transaction_id 和 roll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。这些隐藏列的值不用我们操心,InnoDB存储引擎会自己帮我们生成的。

补充:对于 CHAR(M) 类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。

行溢出数据

VARCHAR(M)类型的列最多可以占用65535个字节。其中的M代表该类型最多存储的字符数量,如果我们使用ascii字符集的话,一个字符就代表一个字节,但VARCHAR(M)类型的列,其实需要占用3部分存储空间:

  • 真实数据
  • 真实数据占用字节的长度
  • NULL值标识,如果该列有NOT NULL属性则可以没有这部分存储空间

如果该VARCHAR类型的列没有NOT NULL属性,那最多只能存储65532个字节的数据,因为真实数据的长度可能占用2个字节。有NOT NULL属性,那最多只能存储65533个字节的数据。

VARCHAR(M)类型的列使用的不是ascii字符集,那M的最大取值取决于该字符集表示一个字符最多需要的字节数。在列的值允许为NULL的情况下,gbk字符集表示一个字符最多需要2个字节,那在该字符集下,M的最大取值就是32766(也就是:65532/2),也就是说最多能存储32766个字符;utf8字符集表示一个字符最多需要3个字节,那在该字符集下,M的最大取值就是21844,就是说最多能存储21844(也就是:65532/3)个字符。

记录中的数据太多产生的溢出

MySQL是以为基本单位来管理存储空间的,我们的记录都会被分配到某个中存储。而一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65532个字节,这样就可能造成一个页存放不了一条记录的尴尬情况。

CompactReduntant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址(当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从而可以找到剩余数据所在的页,如图所示:

不只是 VARCHAR(M) 类型的列,其他的 TEXTBLOB 类型的列在存储数据非常多的时候也会发生行溢出

Dynamic和Compressed行格式

下边要介绍另外两个行格式,DynamicCompressed行格式,MySQL5.7版本的默认行格式就是Dynamic,这俩行格式和Compact行格式挺像,只不过在处理行溢出数据时有点儿分歧,它们不会在记录的真实数据处存储字段真实数据的前768个字节,而是把所有的字节都存储到其他页面中,只在记录的真实数据处存储其他页面的地址。

Compressed行格式和Dynamic不同的一点是,Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。

总结

  1. 页是MySQL中磁盘和内存交互的基本单位,也是MySQL是管理存储空间的基本单位。
  2. InnoDB目前定义了四种行格式:COMPACT/Redundant(特别老)/Dynamic和Compressed。其中Dynamic和Compressed与COMPACT十分相似,只不过在处理行溢出数据时有点儿分歧。
  3. 一个页一般是16KB,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为行溢出

参考内容:

https://juejin.im/book/5bffcbc9f265da614b11b731/section/5c0374a06fb9a049d37ed783

欲知详情,可以购买原版电子书。

喜欢()
评论 (0)