InnoDB存储引擎-表

782 阅读8分钟

4.1. 索引组织表

在InnoDB中,表中的数据都是按照主键排序的,在InnoDB的设计中,每个表都有一个主键,如果创建表时没有显式的声明主键,InnoDB会采取以下规则:

  • 判断是否有唯一的非空索引(Not Null, Unique),如果有,就是新的主键。
  • 如果不符合上述条件,InnoDB会自动创建一个6字节大小的指针作为主键。

对于第一点,主键的选择是根据创建索引的顺序,而不是创建列的顺序

4.2. InnoDB逻辑存储结构

045B583B-1911-4AF1-A3E9-CAA6C5477A54_1_105_c.jpeg

4.2.1. 表空间

表空间(图示最左上)为InnoDB逻辑存储结构的最高层,所有的数据都存放在表空间中,不过可以设置,使得每个表都拥有自己的表空间;不过即使这样,索引和bitmap,回滚信息(undo),插入缓冲索引页,系统事务信息,两次写缓冲等数据依旧放在共享表空间内。

4.2.2. 段

表空间是由多个段构成的,段的管理由InnoDB自动完成

4.2.3. 区

区由连续的页组成,在任何情况下,每个区的大小都是1MB,1024KB,为了保证页的连续性,InnoDB一般会向磁盘连续申请4-5个区。因为默认情况下,一页=16KB,所以一个区中有64个连续的页。

后版本的InnoDB引入了压缩页的概念,每个页可以为8KB或更小,此时一个区中页数量就会相应增加,但是区大小总是1MB。

此外,为了节省空间,在创建表时会先使用32个页大小(32 * 16KB)的碎片页来保存,如果此后空间不够,才会使用区来保存。

4.2.4. 页

页是InnoDB管理的最小单位。可以设置页的大小,但一旦设置完成,就不能再更改,除非使用mysqldump导入导出来重置。

常见的页类型有:

  • 数据页(B-Tree Node)
  • undo页(Undo Log Page)
  • 系统页(System Page)
  • 事务数据页(Transaction System Page)
  • 插入缓冲位图页(Insert Buffer)
  • 插入缓冲空闲列表页(Insert Buffer Free List)
  • 未压缩的二进制大对象页(Uncompressed BOLB Page)
  • 压缩的二进制大对象页(Compressed BLOB Page)

4.2.5. 行

InnoDB是面向列的,也就是说数据是按行存放的。对于每行存放的记录也是有要求的,比如最多7992((16KB / 2) - 200)行。

4.3. InnoDB行记录格式

InnoDB的行记录方式表示页中保存着的是一行行的记录。

4.3.1. Compact行记录格式

Compact是MySQL5.0+引入的,对于这种格式来说,一个页中存放的数据越多,其性能就越高。来看看它的组成格式:

19592650-ADC8-4644-B43E-B2BC04560939_1_105_c.jpeg

现在来介绍各个部分:

  • 变长字段长度列表:逆序记录每个变长字段的长度,如果长度小于255,就用1字节,否则2字节,因此长度不得大于65535。实际上,MySQL规定所有变长列加起来的长度不得大于65535而不是单个列
  • NULL标志位,使用bit标记第几列有空值,如果某一位置有空数据,则使用1表示,比如5列数据的第2和4列为空,则有00001010表示。另外,这个位大小必须是N字节,逆序记录空列的位置。
  • 记录头信息,固定为5字节,每一位的意义相见下表。
  • 实际的列数据。需要注意的是,NULL列不占用任何空间,它仅仅占用一个标示位。另外,还有两个隐藏列:事务ID和回滚指针列,分别是6/7字节,另外还可能在没有主键的情况下增加一个row_id列,6字节。 这里放一篇不错的文章

8BFB892F-AB90-47C6-8E63-D3616531895C_1_105_c.jpeg

这里需要说明一下,n_owned和next_record,都和和page_directory的稀疏槽有关(见后述),后者也和下一条记录有关,这里需要说明的是,每一页中的数据都是逻辑排序的,而不是物理排序,什么意思呢?就是每行数据存储在节点里,节点之间通过指针连接,遍历这个链表可以得到主键序,但是前后节点不一定放在相邻的磁盘上,它们之间通过指针保持有序,所以是逻辑有序。

4.3.2. Redundant行记录格式

这个格式是老版的MySQL支持的,在此略去不表。

4.3.3. 行溢出数据

如果某些列数据过大,则称为行溢出数据,会把它们存在别的地方,而不是数据页中。

一般情况下,InnoDB存储引擎的数据都是放在主键索引的叶子结点(也就是数据页)中(主键索引的叶子结点是一个索引页,但是因为它保存着数据,所以它还是数据页);当发生行溢出时,数据存放在未压缩二进制大对象页(Uncompressed BOLB Page)中。

现在知道了当发生行溢出时的处理措施,那问题来了,当发生行溢出时,数据页放的是啥?

image.png

可见,数据页保存的是前N个数据,然后是一个指向BOLB页的指针,那具体N的值是多少呢?通过实验得知,这个阀值为8098。此外,因为B+树每一页最少放两条记录(只有一条就成了链表,失去了B+Tree的意义),所以判断依据是放在数据页还是BOLB的依据就是看数据页能否放下两条记录

4.3.4. Compressed和Dynamic行记录格式

新版本的InnoDB引入了两种新的行记录格式:Compressed和Dynamic。这两种格式对于BLOB数据采用了完全的行溢出记录方式。即,数据页仅存放20字节的指针,实际的数据存放在偏移页中(OffsetPage)

image.png

另一点是,Compressed会使用zlib算法进行行数据的压缩,因此对于大类型数据能进行非常有效的存储。

CHAR类型的行结构存储

在这里仅说一点,对于多字节字符编码的CHAR数据类型的存储,InnoDB会在内部将其视为变长字符类型。也就是在变长列表中,会进行其长度的记录。在多字节字符情况下,CHAR和VARCHAR基本没有区别。

4.4. InnoDB数据页结构

页类型为B-Tree Node的页既是存放表中行实际数据的地方了。它们就是数据页。一般来说,数据页由以下几个部分组成:

1B3DE76F-F91E-4563-98AD-9F2D9C676050_1_105_c.jpeg 文件头,页头和文件尾用来记录页的信息,比如页在B+树中索引的层数等,用户记录,空闲空间,页目录等,用来记录实际的行记录,因此大小是动态的。

4.4.1. FileHeader

来看一下文件头的组成部分:

image.png

4.4.2. PageHeader

看其组成:

E9F389AC-3B72-40EB-A55F-23B08CA53EFA_1_105_c.jpeg

4.4.3. Infimum和Supremum Record

这是每页都有的两个虚记录,起到了边界指针的作用,Infimum是比当前页主键里最小的一个还小,Supremum则比最大的还大。这两个指针在页创建时建立,且永不会被删除。

774371-20190625095446926-753231622.png

Page页内数据排列乱序,不过逻辑有序。

4.4.4. User Record和Free Space

UserRecord存放实际的行记录,而每次记录被删除,其所在的空间就被释放,然后被加入到FreeSpace里,FreeSpace,空闲空间,记录哪些空间可用。

4.4.5. Page Directory

页目录,顾名思义,用来寻找当前页里面的记录。Page Directory存放着记录的相对位置。这里面放的是称为槽的结构,它是一个稀疏目录,即每个槽含有多个记录的位置(通过保存记录的指针实现位置存储),并不是每个记录都有一个槽的,可能A的槽同时还存放着记录B,C,D。

同时,槽里放着的记录是逻辑有序的,槽与槽之间也是有序的,所以可以通过B+树找到页,通过二分查找找到槽,通过槽里每个记录的next_record遍历找到需要的记录。同时因为每个记录存在n_owned字段,这个字段指出这个记录后面还跟着多少个记录,这样通过遍历去寻找。

官方文档这么解释:

The Page Directory part of a page has a variable number of record pointers. Sometimes the record pointers are called "slots" or "directory slots". Unlike other DBMSs, InnoDB does not have a slot for every record in the page. Instead it keeps a sparse directory. In a fullish page, there will be one slot for every six records.

The slots track the records' logical order (the order by key rather than the order by placement on the heap). Therefore, if the records are 'A''B''F''D' the slots will be (pointer to 'A') (pointer to 'B') (pointer to 'D') (pointer to 'F'). Because the slots are in key order, and each slot has a fixed size, it's easy to do a binary search of the records on the page via the slots.

(Since the Page Directory does not have a slot for every record, binary search can only give a rough position and then InnoDB must follow the "next" record pointers. InnoDB's "sparse slots" policy also accounts for the n_owned field in the Extra Bytes part of a record: n_owned indicates how many more records must be gone through because they don't have their own slots.)

4.4.6. FileTrailer

这个部分用来记录页是否被完整的写入磁盘,

4.5. 约束

4.5.1. 数据完整性

一般来说,数据完整性有三个方式来保证:

  • 实体完整性:表中有一个主键。
  • 域完整性(或字段完整性):保证每列的值满足特定的条件。
  • 参照完整性:保证两张表之间的关系。

4.5.2. 约束的创建和查找

约束的创建无非就是两种:建表时选择和ALTER TABLE语句更新。

对于主键而言,其默认约束名为PRIMARY,对于Unique Key来说,默认约束名和列名一样。

4.5.3. 约束和索引的区别

约束更像是一个逻辑的概念,用来保证数据的完整性;但是索引更像是一个数据结构,既有逻辑概念,也有物理组织方式。