从InnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间(tablespace),表空间又由段(segment)、区(extent)、页(page)组成,页中又包含行的概念(即一条一条的数据)。页在某些文档中被称之为块(block),InnoDB存储引擎的逻辑结构如下
表空间是InnoDB存储引擎逻辑结构的最高层,所有的数据都放在表空间中,默认情况下,InnoDB存储引擎有一个共享表空间ibdata1,即所有的数据都放在这个表空间中。
用户可以通过参数innodb_file_per_table
来控制每张表的数据是放在共享表空间中还是放到单独的表空间中。
如果启动了innodb_file_per_table
参数,只会将表的数据,索引和插入缓冲BitMap页存放到单独的表空间中,其他信息,例如回滚(undo),插入缓冲索引页,系统事务信息,二次写缓冲(DOuble write buffer)还是存放于原来的共享表空间内,所以共享表空间还是会不断变大。
共享表空间不会缩小,共享表空间会判断内部的数据是否可用,如果没有用的话就会标记为可用空间,供下次使用。
表空间是由各个段组成的,例如数据段,索引段,回滚段等
在InnoDB存储引擎中,对段的管理都是有存储引擎所完成,DBA没有必要对其进行控制,这与Oracle数据库中的自动段空间管理(ASSM)类似,简化了DBA对段的管理。
区是由连续页组成的空间,在任何情况下每个区的大小为1M,为了保证区中页的连续性,InnoDB会一次性从磁盘申请4-5个区。默认情况下InnoDB中页的大小为16KB,即默认情况下一个区有64个连续的页。
InnoDB 1.0x版本开始引入压缩页,即每个页的大小可以通过参数KEY_BLOCK_SIZE
自定义页大小,例如2KB,4KB,8KB,因此所对应区的页的梳理为512,256,128.因为区的大小是固定为1M,所以区所对应的页的个数与页大小相关
开启KEY_BLOCK_SIZE
后,每个数据库都有单独的文件夹,文件夹里有自己的表空间
例如进入mybatisplus数据库可以看到对应表的ibd文件
上述提到InnoDB会一次性从磁盘申请4-5个区,那我创建一个新表testcreatetablesize,观察其大小会发现为96KB,上图所示。一个区的大小为1M,idb文件远远小于1M,这是为什么呢?
这是因为在创建表时会先寻找32个页大小的碎片页(fragment page)来存放数据,使用完了之后才是64个连续页的申请,好处是对于小表或者undo这类的段,开始时申请较少的空间,节省磁盘容量的开销。
页是InnoDB磁盘管理的最小单位。在InnoDB存储引擎中,默认每个页的大小为16KB,从InnoDB 1.2x版本开始,通过参数innodb_page_size
来设置页的大小。如果设置了innodb_page_size
,就不可以对其进行修改。InnoDB中常见的页类型有:
File Header主要是记录页的一些头部信息,由8个部分组成,共占用38个字节。
File Header详细信息
名称 | 大小(字节) | 说明 |
---|---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 4 | mysql在4.0.14之前的版本时,改值为0,在之后的版本中,改值代表checksum值,checksum与File Trailer相对应,后续会说明 |
FIL_PAGE_OFFSET | 4 | 表空间中页的偏移量,FIL_PAGE_OFFSET表示该页在所有页中的位置 |
FIL_PAGE_PREV | 4 | 当前页的上一个页,因为页是双向列表 |
FIL_PAGE_NEXT | 4 | 当前页的下一个页 |
FIL_PAGE_LSN | 8 | 改值代表该页最后被修改的日志序列位置 LSN(log sequence number) |
FIL_PAGE_TYPE | 2 | InnoDB存储引擎页的类型,详细信息见下表 |
FIL_PAGE_FILE_FLUSH_LSN | 8 | 该值仅在系统表空间中一个页的定义,代表文件至少被更新到了该LSN值,对于独立表空间,改值都为0 |
FILE_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 4 | 改值代表该页属于哪个表空间 |
FIL_PAGE_TYPE详细信息说明
名称 | 十六进制 | 解释 |
---|---|---|
FIL_PAGE_INDEX | 0x45BF | B+树叶节点 |
FIL_PAGE_UNDO_LOG | 0x0002 | undo log页 |
FIL_PAGE_INODE | ox0003 | 索引节点 |
FIL_PAGE_IBUF_FREE_LIST | 0x0004 | insert buffer 空闲列表 |
FIL_PAGE_TYPE_ALLOCATED | 0x0000 | 该页为最新分配的 |
FIL_PAGE_IBUF_BITMAP | 0x0005 | insert buffer 位图 |
FIL_PAGE_TYPE_SYS | 0x0006 | 系统页 |
FIL_PAGE_TYPE_TRX_SYS | 0x0007 | 事务系统数据 |
FIL_PAGE_TYPE_FSP_HDR | 0x0008 | file space header |
FIL_PAGE_TYPE_XDES | 0x0009 | 扩展描述页 |
FIL_PAGE_TYPE_BLOB | 0x000A | BLOB页 |
该部分用于记录数据页的状态信息,由14个部分组成,共占用56字节
在InnoDB存储引擎中,每个数据页都有两个虚拟的行记录,用于限定记录的边界,Infimum记录是比该页中的任何主键值都小的值,Supremum指比任何可能大的值还要大的值。在页创建的时候,这两个虚拟的行记录就会被建立,并且任何情况下都不会被删除,在Compact行格式和Redundant行格式下,其占用的字节数不同
User Record即实际存储行记录的内容
Free Space就是空闲空间,同样也是链表数据结构,在一条记录被删除后,该空间会被加入到空闲链表中
Page Directory(页目录)中存放了记录的相对位置(这里存放的是页的相对位置,不是偏移量),有时候这些记录指针称为slots(槽)或目录槽(Directory slots)。与其他数据库系统不同的是,在InnoDB中并不是每个记录都拥有一个槽,InnoDB存储引擎的槽是一个稀疏目录(sparse directory)即一个槽中可能包含多个记录。伪记录Infimum的n_owned的值总为1,记录supremum的n_owned的取值范围为【1,8】,其他用户记录n_owned的取值范围为【4,8】当记录被插入或者被删除时需要对槽进行分裂或平衡的维护操作。
在slots中记录按照索引键值顺序存放,这样可以利用二叉查找迅速找到该指针,(只是粗略的结果)具体的精确的结果需要行记录投(record header)中的next_record来继续查找相关记录。
B+树索引不能找到具体的某一条记录,它只能找到对应的页,加载页,通过Page Directory进行二分查找,寻到粗略的位置,再通过record header的next_record来查找到对应的行记录
为了检查页是否已经完整的写入磁盘(因为有可能从缓冲刷入磁盘时遇到突发情况,例如断电等),InnoDB存储引擎的页中设置了File Trailer部分。
File Trailer只有一个FIL_PAGE_END_LSN部分,占用8个字节。前4个字节代表该页的checksum值,对应File Header的FIL_PAGE_SPACE_OR_CHKSUM的checksum,后4个字节对应File Header的FIL_PAGE_LSN,如果两个都保持一致(注意:checksum是通过checksum函数来进行比较的,不是简单的等值比较),则保证页的完整性。
在默认情况下,InnoDB存储引擎每次从磁盘读取一个页就会检查页的完整性,就是通过File Trailer来进行检测,所以会有一定的开销,可以通过参数innodb_checksums
来开启或者关闭对页完整性的检查
Mysql5.6.6版本新增参数innodb_checksum_algorithm
,通过该参数来控制检测函数checksum的算法,默认值为crc32,可以设置的值有innodb、crc32、none、strict_innodb、strict_crc32、strict_non。
InnoDB存储引擎和大多数数据库一样,记录是以行的形式进行存储的。InnoDB存储引擎提供了Compact和Redundant两种格式来存放行记录,Mysql5.1中默认使用Compact行格式,可以通过命令SHOW TABLE STATUS LIKE 'table_name'
来查看表的行格式,其中row_format
就是行格式
边长字段长度列表: 记录非NULL变长字段长度列表(逆序放置),其长度为,若列的长度小于255,则占用1字节,若大于255,则用两个字节表示。
所以varchar类型的最大长度限制为 2^16 - 1 = 65535
NULL标志位: 该为指定了该行数据中是否有NULL值,有则用1表示,该部分占用1字节
记录头信息: 该位置占用5个字节,具体信息如下
Total部分就是实际存储每个列的数据,注意:NULL不占用该部分的空间,NULL只会占用NULL标志位。
每行数据除了用户定义的数据外,还有两个隐藏列,分别是事务ID列和回滚指针列,分别占用6字节和7字节,如果InnoDB没有显示的定义主键,且其余的列不符合主键的要求,则会隐式地创建6个字节的rowid列来充当主键,聚簇索引就按照这个rowid来生成。
Redundant是Mysql5.0版本之前InnoDB行记录存储的方式,Mysql5.0支持Redundant是为了兼容之前的版本的页格式。其格式如下:
字段长度偏移列表: Redundant行记录格式的头部是一个字段长度偏移列表,也是按照列的顺序逆序存储,如果列的长度小于255字节,则用1字节表示,如果大于255字节,则用2字节表示
记录头信息: Redundant行记录头占用6字节,其具体信息如下图所示
InnoDB存储引擎将一条记录的某些列存储在真正的数据页面之外,一般认为BLOB,LOB这类大对象类型会将数据存储在数据页外面。但是这个理解有些偏差,BLOB可以不将数据存放在溢出页面,而且varchar类型数据也有可能出现行溢出。
上述提到varchar类型可以存放65535字节,但是实际上并不能存储65535字节。
不支持65535字节长度的varchar是因为还会有别的开销,实测varchar可以存储65532字节
如果执行上述例子时,没有将SQL_MODE设置为严格模式,可以建立varchar 65535字节成功,但是会有warning,会将varchar转为TEXT
上述我们创建的 varchar 65532的字符集是latin1的,如果换位GBK或UTF-8,就不能建立 varchar 65532了。
所以可以得出varchar(N)的N指的是字符的长度,上述说的65535指的是字节
注意:varcahr中的65535指的是所varchar的长度和,即一列中varchar的长度和不能超过65535
思考一个问题,varchar如果占用的字节最大 65532,但是InnoDB的最小管理单位是页 16KB,即16384字节,这也存不下呀!
一般情况下,InnoDB存储引擎的数据都是存放在页类型为B-Tree node中,上述情况就涉及到了行溢出的概念,当发生行溢出时,数据就存放在页类型为Uncompress BLOB页中
那么varchar多少字节会保留到数据页中,超过多少又会保存到BLOB页中呢?
我们知道页中至少要有两条记录(不然就失去了B+TREE的意义,就变为链表了)。所以如果页中只能存放一条记录,那么InnoDB就会将行数据放到溢出页中
InnoDB1.0X版本引入新的文件格式,以前支持的Compact与Redundant格式称为Antelope文件格式,新的文件格式称为Barracuda文件格式,Barracuda文件格式下拥有两种新的行记录格式,分别是 Compressed与Dynamic
这两种新的行格式对于行溢出的情况采用了完全溢出的方式,如果出现行溢出,则数据页中只存储20字节的指针,实际数据都在Off Page中,而之前的Compact与Redundant两种行格式会存放溢出数据的前768字节
Compressed行记录格式的另一个功能:存储在其中Off Page的数据会以zlib算法进行压缩,有效的进行数据的存储
从Mysql4.1版本开始,char(N)中的N指的是字符的长度,而不是之前的字节的长度
所以可以理解char类型为变长字符类型。