MySQL阅读笔记——3.InnoDB
InnoDB引擎设计了4种行格式Compact、Redundant、Dynamic、Compressed;我们可以在创建或者修改表的时候通过 ROW_FORMAT 指定行格式。
当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中,这种现象称为"行溢出
1.MySQL数据类型存储所占字节数以及长度
1.1 数据类型存储占用字节数(大小)
数据类型存储占用字节数分为定长和变长,一般数字类型(如int/float/double/datetime等)是定长的,而文本/二进制相关(如char/varchar/text/blob等)是变长的,与建表时指定的字符集相关。
1.2 长度(宽度)
建表时候在类型后面通过括号指定类型长度,而类型长度对不同的类型表示的意义是不同的。对一般数值类型表示显示出来的数据宽度(高位不足用“0”填充,但不会实际存储)。对字符串类型(主要是char和varchar)表示的是该列实际能存储的字符数(对于char如果不足长度则会在列右侧填充空格到达指定长度,而varchar则不会)。
指定表中的列为zerofill查询的时候可以看到数据类型告位不足用“0”填充。
*.navicat不支持zerofill显示,需要到命令行下可以看到效果
1.3 行格式
行首变长字段列表用1~2个字节存储非NULL变长字段的实际存储字节数 NULL值列表存储允许为空的字段是否为空(1为NULL;0不为NULL,每个允许位空的字段占用一位。),如果列值为NULL则不会再真实数据处存储,从而节省空间。
NULL值列表必须为字节的整数倍,不足高位用0补齐
记录头信息是描述信息
名称 | 大小(单位:bit)a | 描述 |
---|---|---|
预留位1 |
1 |
没有使用 |
预留位2 |
1 |
没有使用 |
delete_mask |
1 |
标记该记录是否被删除(0:没删除,1:已删除) |
min_rec_mask |
1 |
B+树的每层非叶子节点(内节点)中的最小记录都会添加该标记 |
n_owned |
4 |
表示当前记录拥有的记录数 |
heap_no |
13 |
表示当前记录在记录堆的位置信息,从2开始往后递增(0是最小记录infimum地址,1是最大记录supermum地址) |
record_type |
3 |
表示当前记录的类型,0 表示普通记录,1 表示B+树非叶子节点(内节点)记录,2 表示最小记录,3 表示最大记录 |
next_record |
16 |
表示下一条记录的相对位置,(即:当前数据距下条记录记录头和真实数据之间位置的偏移量,负数为该记录之前,正数为该记录之后,如果没有下一条数据则为0) |
每一行数据除了级记录真实数据和额外信息外还会记录隐藏列:每条记录都会添加事务ID列DB_TRX_ID和回滚指针列DB_ROLL_PTR,在没有自定义主键并且没有Unique键情况下还会创建隐藏主键列DB_ROW_ID,隐藏主键全数据库唯一递增。
Redundant(MySQL5.0之前版本)
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 |
1 |
没有使用 |
预留位2 |
1 |
没有使用 |
delete_mask |
1 |
标记该记录是否被删除 |
min_rec_mask |
1 |
B+树的每层非叶子节点中的最小记录都会添加该标记 |
n_owned |
4 |
表示当前记录拥有的记录数 |
heap_no |
13 |
表示当前记录在页面堆的位置信息 |
n_field |
10 |
表示记录中列的数量 |
1byte_offs_flag |
1 |
标记字段长度偏移列表中每个列对应的偏移量是使用1字节还是2字节表示的 |
next_record |
16 |
表示下一条记录的相对位置 |
MySQL
对一条记录占用的最大存储空间是有限制的,除了BLOB
或者TEXT
类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535
个字节。
在 Compact 和 Redundant 中对应占用存储空间大的列,在记录的真实数据处只记录一部分数据(768个字节数据),其余数据记录在其他页中用链表连接,记录真实数据处用20个字节指向其他页面链表第一个页面地址。这个过程叫做 行溢出 存储超出786字节的数据的那些页面称为 溢出页 。(行溢出 其实是某些列数据太大导致的 列溢出)
不只是 VARCHAR(M) 类型的列,其他的 TEXT、BLOB 类型的列在存储数据非常多的时候也会发生
行溢出
。
Dynamic和Compressed格式
MySQL5.7以后表的默认行格式是Dynamic,Dynamic大部分和Compact行格式一致,只是在处理行溢出情况时没有在真实数据处存储数据(768字节)而是将所有数据以链表结构存储到其他页面Compressed对页面进行了压缩节省空间。
2.1记录头信息(Compact行格式)
记录按照主键大小在内存中形成一个 单链表,最大记录(Supermum)的next_record是0。
next_record指向每条记录的记录头和记录数据之间的位置,这个位置向左是记录头(变长字段长度列表、Null值列表逆序存放),向右是真实数据。这样可以使记录中位置靠前的字段和它们对应的字段长度信息在内存中的距离更近,可能会提高高速缓存的命中率。
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 |
1 |
没有使用 |
预留位2 |
1 |
没有使用 |
delete_mask |
1 |
标记该记录是否被删除,删除数据并不会真的删除 |
min_rec_mask |
1 |
B+树的每层非叶子节点中(目录项)的最小记录都会添加该标记 |
n_owned |
4 |
表示当前记录拥有的记录数,即该组内拥有的记录数 |
heap_no |
13 |
表示当前记录在记录堆的位置信息,从2开始往后递增(0是最小记录infimum地址,1是最大记录supermum地址) |
record_type |
3 |
表示当前记录的类型, 0表示普通记录(叶子节点记录), 1表示B+树非叶节点记录(目录项记录也叫内节点), 2表示最小记录, 3表示最大记录 |
next_record |
16 |
表示下一条记录的相对位置,(即:当前数据距下条记录**记录头和真实数据之间位置**的偏移量,负数为该记录之前,正数为该记录之后,如果没有下一条数据则为0 |
MySQL中页是存储数据的基本单元,也是磁盘和内存交换的基本单位,页大小一般为16KB(在MySQL初始化的时候通过innodb_page_size参数指定),MySQL为不同目的设计了不同页,存储数据的页官方叫做索引页(Index),为了方便这里称为数据页。一个数据页只能存储一张表的数据。
删除记录并不会真的删除,而是设置记录的头部的属性标志(delete_mask置为1表示删除0未删除),被删除的(delete_mask设置为1)的数据会组成一个 垃圾链表 ,链表的空间被称为 可重用空间 如果有新纪录插入到表中,则会覆盖 可重用空间 MyISAM和InnoDB引擎可以使用OPTIMIZE TABLE命令重新利用未使用空间,并且整理碎片 。
将delete_mask设置为1 和 将记录加入到垃圾链表 是两个操作,涉及到数据库事务(InnoDB引擎)
在InnoDB中页和块的是同样的概念,但是在MyISAM中块的大小是4096字节
2.2 数据页信息
数据页是一个双链表结构,在FileHeader中记录页号以及上一页页号和下一页页号,校验和等。数据页采用双向链表结构是为了范围查找效率更高
大小(单位Byte) | 描述 | |
---|---|---|
FileHeader |
38 |
页是一个双向链表结构,该区域存储了上一个页和下一个页的页号,该区域所有页通用(存储有校验和页类型) |
PageHeader |
56 |
记录数据页的状态信息(其他页类型没有这个区域) |
Infimum+Supermum |
26 |
最小记录和最大记录,每个记录有5字节的头信息和8字节的固定数据部分 |
FreeSpace |
表内空闲数据空间 |
|
PageDirectory |
页面内槽目录,存储每个槽的偏移地址 |
|
FileTrailer |
8 |
防止页同步磁盘过程中丢失,前4字节是校验和与FileHeader对应,保证该页正确性,后4字节是LSN(类似乐观锁机制) |
所有正常记录(包括Infimun和Supermum,不包括已经删除的记录)在数据页(索引页)中按照主键由小到大串联成一个单链表,这个单链表可以划分为多个组,头信息的n_owned表示该组有多少条数据(除了Supermun有1-8条,其余都是4-8条),每个组的最后一条数据(主键值最大的数据)的偏移量记录在靠近页尾部分,这个靠近页尾的部分就是PageDirectory,其中记录的每一项偏移量就叫做 槽(solt)。
每个PageDirectory中默认存储了Infimun(n_owned值永远为1)和Supermum偏移地址。
查找页内数据:通过二分法确定槽,取它前一个槽的next_record找到目的槽的实际地址,顺序查找指定值。
插入页内数据:从PageDirectory找到比d插入数据的主键大但是差值最小的槽,将该槽的n_owned的值+1,如果槽的记录数超过8个,再次插入时会分裂为4、5记录数的两组,并在PageDirectory中新增一个槽。
2.2.1 Page Header结构
名称 | 占用空间大小 | 描述 |
---|---|---|
PAGE_N_DIR_SLOTS |
2 字节 |
在页目录中的槽数量 |
PAGE_HEAP_TOP |
2 字节 |
还未使用的空间最小地址,也就是说从该地址之后就是Free Space |
PAGE_N_HEAP |
2 字节 |
本页中的记录的数量(包括最小和最大记录以及标记为删除的记录) |
PAGE_FREE |
2 字节 |
第一个已经标记为删除的记录地址(各个已删除的记录通过next_record 也会组成一个单链表,这个单链表中的记录可以被重新利用) |
PAGE_GARBAGE |
2 字节 |
已删除记录占用的字节数 |
PAGE_LAST_INSERT |
2 字节 |
最后插入记录的位置 |
PAGE_DIRECTION |
2 字节 |
记录插入的方向 |
PAGE_N_DIRECTION |
2 字节 |
一个方向连续插入的记录数量 |
PAGE_N_RECS |
2 字节 |
该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID |
8 字节 |
修改当前页的最大事务ID,该值仅在二级索引中定义 |
PAGE_LEVEL |
2 字节 |
当前页在B+树中所处的层级 |
PAGE_INDEX_ID |
8 字节 |
索引ID,表示当前页属于哪个索引 |
PAGE_BTR_SEG_LEAF |
10 字节 |
B+树叶子段的头部信息,仅在B+树的Root页定义 |
PAGE_BTR_SEG_TOP |
10 字节 |
B+树非叶子段的头部信息,仅在B+树的Root页定义 |
2.2.2 File Header结构
名称 | 占用空间大小 | 描述 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM |
4 字节 |
页的校验和(checksum值) |
FIL_PAGE_OFFSET |
4 字节 |
页号 |
FIL_PAGE_PREV |
4 字节 |
上一个页的页号 |
FIL_PAGE_NEXT |
4 字节 |
下一个页的页号 |
FIL_PAGE_LSN |
8 字节 |
页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number) |
FIL_PAGE_TYPE |
2 字节 |
该页的类型 |
FIL_PAGE_FILE_FLUSH_LSN |
8 字节 |
仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID |
4 字节 |
页属于哪个表空间 |
2.2.3 页类型对照
类型名称 | 十六进制 | 描述 |
---|---|---|
FIL_PAGE_TYPE_ALLOCATED |
0x0000 | 最新分配,还没使用 |
FIL_PAGE_UNDO_LOG |
0x0002 | Undo日志页 |
FIL_PAGE_INODE |
0x0003 | 段信息节点 |
FIL_PAGE_IBUF_FREE_LIST |
0x0004 | Insert Buffer空闲列表 |
FIL_PAGE_IBUF_BITMAP |
0x0005 | Insert Buffer位图 |
FIL_PAGE_TYPE_SYS |
0x0006 | 系统页 |
FIL_PAGE_TYPE_TRX_SYS |
0x0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR |
0x0008 | 表空间头部信息 |
FIL_PAGE_TYPE_XDES |
0x0009 | 扩展描述页 |
FIL_PAGE_TYPE_BLOB |
0x000A | 溢出页 |
FIL_PAGE_INDEX |
0x45BF | 索引页,也就是我们所说的数据页 |