竹笋

首页 » 问答 » 常识 » 存储引擎InnoDB详解,从底层看
TUhjnbcbe - 2023/10/24 19:04:00

InnoDB一个支持事务安全的存储引擎,同时也是mysql的默认存储引擎。本文主要从数据结构的角度,详细介绍InnoDB行记录格式和数据页的实现原理,从底层看清InnoDB存储引擎。

InnoDB简介

大家都知道mysql中数据是存储在物理磁盘上的,而真正的数据处理又是在内存中执行的。由于磁盘的读写速度非常慢,如果每次操作都对磁盘进行频繁读写的话,那么性能一定非常差。为了上述问题,InnoDB将数据划分为若干页,以页作为磁盘与内存交互的基本单位,一般页的大小为16KB。这样的话,一次性至少读取1页数据到内存中或者将1页数据写入磁盘。通过减少内存与磁盘的交互次数,从而提升性能。

其实,这本质上就是一种典型的缓存设计思想,一般缓存的设计基本都是从时间维度或者空间维度进行考量的:

时间维度:如果一条数据正在在被使用,那么在接下来一段时间内大概率还会再被使用。可以认为热点数据缓存都属于这种思路的实现。空间维度:如果一条数据正在在被使用,那么存储在它附近的数据大概率也会很快被使用。InnoDB的数据页和操作系统的页缓存则是这种思路的体现。

高性能MySQL(第3版)(博文视点出品)京东月销量好评率98%无理由退换京东配送官方店¥购买

InnoDB行格式

mysql是以记录(一行数据)为单位向数据表中插入数据的,这些记录在磁盘上的存放方式称为行格式。mysql支持4种不同类型的行格式:Compact、Redundant(比较老,本文就不具体介绍了)、Dynamic、Compressed。

我们可以在创建或修改表的语句中指定行格式:

比如,我们要创建一个行格式为Compact,字符集为ascii的数据表record_format_demo,sql如下:

假设我们向record_format_demo表中插入了2行数据:

COMPACT行格式

从上图可以看出,一条完整的记录包含记录的额外信息和记录的真实数据两大部分。

记录的额外信息

记录的额外信息主要包含3类:

变长字段长度列表、NULL值列表和记录头信息。

变长字段长度列表

mysql中支持一些变长数据类型(比如VARCHAR(M)、TEXT等),它们存储数据占用的存储空间不是固定的,而是会随着存储内容的变化而变化。为了准确描述这种数据,这种变长字段占用的存储空间要同时包含:

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

我们以record_format_demo第一行数据为例。由于c1、c2和c4都是变成数据类型(VARCHAR(10)),因此要将这3列值得长度保存在记录的开头处。

另外需要注意的一点是,变长字段长度列表中只存储值为非NULL的列内容占用的长度,值为NULL的列的长度是不储存的。也就是说对于第二条记录来说,因为c4列的值为NULL,所以第二条记录的变长字段长度列表只需要存储c1和c2列的长度即可。

NULL值列表

对于可为NULL的列,为了节约存储空间,mysql不会将NULL值保存在记录的真实数据部分。而是会将其保存在记录的额外信息里面的NULL值列表中。

具体的做法是先统计表中允许存储NULL值的列,然后将每个允许存储NULL值的列对应一个二进制位(1:值为NULL,0:值不为NULL)用来表示是否存储NULL值,并按照逆序排列。MySQL规定NULL值列表必须用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0。

对应record_format_demo表中,c1、c3、c4都是允许存储NULL值的。前两条记录在填充了NULL值列表后的示意图就是这样:

记录头信息

记录头信息是由固定的5个字节(40位)组成,不同的位代表不同的含义:

暂时不详细展开。

记录的真实数据

记录的真实数据除了包含各列具体的数据外,还会自动添加一些隐藏列数据。

实际上这几个列的真正名称其实是:DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR,为了美观才写成了row_id、transaction_id和roll_pointer。

只有当数据库没有定义主键或者唯一键时,隐藏列row_id才会存在,并且将其作为数据表主键。

因为表record_format_demo并没有定义主键,所以MySQL服务器会为每条记录增加上述的3个列。现在看一下加上记录的真实数据的两个记录的数据结构:

CHAR(M)列的存储格式

对于CHAR(M)类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到变长字段长度列表。另外有一点还需要注意,变长字符集的CHAR(M)类型的列要求至少占用M个字节,而VARCHAR(M)却没有这个要求。比方说对于使用utf8字符集的CHAR(10)的列来说,该列存储的数据字节长度的范围是10~30个字节,即使我们向该列中存储一个空字符串也会占用10个字节。

行溢出数据

VARCHAR(M)最多能存储的数据

MySQL对一条记录占用的最大存储空间是有限制的,除了BLOB或者TEXT类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过个字节。可以不严谨的认为,mysql一行记录占用的存储空间不能超过个字节。这个个字节除了列本身的数据之外,还包括一些其他的数据(storageoverhead),比如说我们为了存储一个VARCHAR(M)类型的列,其实需要占用3部分存储空间:

真实数据真实数据占用字节的长度NULL值标识,如果该列有NOTNULL属性则可以没有这部分存储空间假设varchar_size_demo只有一个VARCHAR类型的字段,那么该字段最大占用的个字节。因为真实数据的长度可能占用2个字节,NULL值标识需要占用1个字节。如果该VARCHAR类型的列没有NOTNULL属性,那最多只能存储个字节的数据。如果该列是ascii字符集,对应的最大字符数最大为;如果是utf8字符集,则对应的最大字符数为。

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

我们以ascii字符集下的varchar_size_demo表为例,插入一条记录:

mysql中磁盘与内存交互的基本单位是页,一般为16KB,个字节,而一行记录最大可以占用个字节,这就造成了一页存不下一行数据的情况。在Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址,从而可以找到剩余数据所在的页,如图所示:

这种在本记录的真实数据处只会存储该列的前个字节的数据和一个指向其他页的地址,然后把剩下的数据存放到其他页中的情况就叫做行溢出,存储超出字节的那些页面也被称为溢出页。

行溢出的临界点

MySQL中规定一个页中至少存放两行记录。以上边的varchar_size_demo表为例,它只有一个列c,我们往这个表中插入两条记录,每条记录最少插入多少字节的数据才会行溢出的现象呢?这得分析一下页中的空间都是如何利用的。

每个页除了存放我们的记录以外,也需要存储一些额外的信息,大概个字节。每个记录需要的额外信息是27字节。假设一个列中存储的数据字节数为n,如要要保证该列不发生溢出,则需要满足:

+2×(27+n)结果是n。也就是说如果一个列中存储的数据小于个字节,那么该列就不会成为溢出列。如果表中有多个列,那么这个值更小。

Dynamic和Compressed行格式

mysql中默认的行格式就是Dynamic。

Dynamic和Compressed行格式和Compact行格式很像,只是在处理行溢出数据上有差异。Dynamic和Compressed行格式不会在记录的真实数据出存放前个字节,而是将所有字节都存储在其它页面中。

Compressed行格式会采用压缩算法对页面进行压缩,以节省空间。

InnoDB数据页结构

我们已经知道页是InnoDB管理存储空间的基本单位,一个页的大小一般是16KB。InnoDB为了不同的目的设计了许多不同类型的页,我们这里主要

1
查看完整版本: 存储引擎InnoDB详解,从底层看