InnoDB的逻辑存储结构学习

InnoDB存储引擎在存储设计方面之前一直是迷迷糊糊,这几天在读《MySQL技术内幕 InnoDB存储引擎》,有点了解,特意记下消化一下^^

我们知道InnoDB是使用表空间结构来进行管理的。默认情况下InnoDB有一个共享表空间ibdata1,即所有的数据都存放在这个表空间内,但是我们可以设定innodb_file_per_table为ON使每一张表的数据可以单独放在一个表空间里


虽然我们启动了innodb_file_per_table参数选项,但是注意的是每张表的表空间存放的只是数据,索引和插入缓冲,其他的数据如撤销信息,系统事务信息等还是存放在原来的共享表空间里。

而在InnoDB存储引擎表中,每一张表都有一个主键,假如我们没有在创建表的时候显式定义主键,则InnoDB会按照如下方式选择或者创建主键:

1、首先表中是否有非空唯一索引,如果有,那么该列即为主键;

2、否则它会自动创建一个6个字节大小的指针。


大致了解InnoDB的表空间概念之后我们看一下他的逻辑存储结构,如下图:


清晰明了,它是由段(segment),区(extent,有书也叫簇),页(page),行(row)组成。


段:

段是用来管理物理文件,构成索引,表,回滚段的基本元素。常见的段有数据段,索引段,回滚段等。创建一个索引(B+树)会创建两个段,分别是内节点段和叶子段,内节点段用来管理或存储B+树中非叶子结点的数据,叶子段用来管理或存储B+树中叶子结点的数据。在随着索引数据量的增大时,所有的新存储空间申请都是从段这个概念开始的。


区:

区是构成段的基本元素,一个段由若干个区组成,区是由64个连续的页组成,每个页大小为16kb,所以每一个区大小为1MB,我们在新建一个innodb的表时候,即使没有插入任何数据,他的大小也是96KB,而区是64个连续的页,按道理应该是1MB才对的,之所以会这样,是在每个段开始时,先会有32个页大小的碎片页来存放数据,只有这些页使用完之后才是64个连续的页申请,同时我们知道,因为段之间是没有位置关系,而每一个段都是一个或者多个区组成,所以区之间也是没有物理位置关系。


页:

页是区的组成单位,常见的页类型有如下:

数据页(B_tree Node)

Undo页(Undo Log Page)

系统页(System Page)

事务数据页(Transaction system Page)

插入缓冲位图页(Insert Buffer Bitmap)

插入缓冲空闲列表页(Insert Buffer Free List)

未压缩的二进制大对象页(Uncompressed Blob Page)

压缩的二进制大对象页(Compressed BLOB Page)

一个区默认是64个页面,这个数量通常称为区的大小,在逻辑上页面号从小到大连续的,并且在物理上也是连续的,再向表中插入数据时,如果一个页面被写完,系统就会从当前区分配一个新的空闲页使用,而当区中64个页面(默认)就会从所在的段中分配一个新的区供使用。

行:

InnoDB存储引擎是面向行的,也就是说数据的存放按行进行存放,每一个页存放的行记录也是有硬性规定的,而在MySQL中,为了帮助管理升级与降级以及运行不同版本的MySQL系统,使用了命名文件格式,目前支持的是Antelope和Barracuda两种。

Antelope支持compact和redundant两种行存储格式;Barracuda是最新的文件存储格式,它支持COMPRESSED和DYNAMIC行存储格式

在目前的版本(使用的是5.7)文件存储格式已经更改为Barracuda了,在这之前是Antelope。

而在新的行存储格式中对于过长的BLOB和VARCHAR等字段,则是将它们存储在单独分配的溢出页,在数据页中只存放20个字节的指针,同时将长度大于或者等于768字节的固定长度字段编码为可变长字段。

默认的行存储格式由innodb_default_row_format定义,默认值是DYNAMIC,我们可以在建表时候使用ROW_FORMAT指定行格式,如下图


Compact行记录格式:

变长字段长度列表  NULL标志位 记录头信息 列1数据 列2数据 ……

如上可以知道Compact行格式,它的首部是一个非NULL变长字段长度列表,当列的长度小于255字节,用1字节表示,若大于255字节,用2个字节表示,变长字段长度最大不超过2个字节,这也解释了MySQL中varchar的最大长度为65535,因为2个字节为16位,2^16=65535;还需记住它是逆序存放值的。

第二个部分是NULL标志位,该位指示该行数据中是否有NULL值,用1表示;

第三部分记录头信息,固定占用5个字节:

名称 大小(bit) 描述
() 1 未知
() 1 未知
deleted_flag 1 该行是否已被删除
min_rec_flag 1 为1,如果该记录是预先被定义为最小的记录
n_owned 4 该记录拥有的记录数
heap_no 13 索引堆中该条记录的排序记录
record_type 3

该记录类型000=普通,001=B+树节点指针,010=Infimum

011=Supremum,1xx=保留

next_recorder 16 页中下一条记录相对位置

Redundant行记录格式:

它是MySQL5.0版本之前的InnoDB行记录存储方式,只是为了向前兼容,它的存储方式如下:

字段长度偏移列表 记录头信息 列1数据 列2数据 列3数据 ……

首部是长度偏移列表,同样是逆序放置值,当列的长度小于255字节,用1字节表示;若大于255字节,用于2个字节表示。

第二个部分为记录头信息,但是不同于Compact行格式,它占用6个字节

Compressed与Dynamic行记录格式:

如前面所说,再对存放BLOB的数据时候完全采用行溢出方式,在数据页只存放20个字节的指针,实际数据都存放在BLOB page中,而在这之前的两个格式中会存放768前缀字节。

Compressed行记录格式的另一个功能就是,存储在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能进行非常有效的存储。