3.4 行溢出
我们在介绍另外两种行格式Dynamic
和Compressed
之前,先介绍下行溢出。
InnoDB存储引擎可以将一条记录中的数据存储在真正的数据页面之外。下面将举例说明这一点,先创建一个数据表。
65535字节是VarCahr
类型存储的最大长度,而ASCII
码一个字符占一个字节,因此我们指定的VARCHAR(65535)
就表示其存储的字符数是65535,这些字符占65535字节,正好是VARCHAR数据类型存储字节的上限。
如果您还不理解就可以不指定字符集试试。
上面的语句将会报错。
这是因为不指定字符集时,默认使用的是utf-8,一个字符占3个字节,因此存储的字符上限就是65535/3=21845。
言归正传,我们再来执行下最开始的建表语句。
居然还是报错了,错误信息如下。
这是为什么呢?其实是因为VARCHAR
的数据是变长的,需要2个字节额外的空间来记录数据的长度,1个字节标识NULL值信息,因此存储空间不能够达到65535字节,只能达到65532字节。
当然,我们可以加上非空约束,这样就不用记录NULL值列表了。
我们之前介绍过一个页的大小是16kb,也就是16384字节,而现在我们一个字段就比它大。这就会导致行溢出。
在Compact
和Ruduntant
两种行格式中,对于占用空间非常大的列,在存储真实数据时只会存储真实数据的一部分。将剩余的数据存储到其它页中进行分页存储。
3.5 Dynamic和Compressed行格式
Dynamic和Compressed行格式与Compact大体是相同的,不过这两种行格式对于行溢出的处理策略与Compact不同。
它们采取了完全行溢出的策略。也就是数据页不存储任何溢出真实数据,只是存储指针,将真实数据完全存储到其它页中。
Compressed还采用了zlib算法对数据进行压缩,因此对于BLOB、TEXT、VARCHAR等大长度类型的数据能够进行有效的存储。
3.6 Redundant行格式
Redundant是Mysql5.0之前InnoDB的行格式。Mysql5.0支持Redundant是为了兼容以前版本的页格式,其格式如下。我们可以对比之前Compact行格式。
我们可以发现,Redundant采用字段长度偏移列表来定位数据,而Compact采用变长字段长度列表和Null值列表。如果是Compact行格式,不是变长数据就不会记录变长字段长度列表。而Redundant行格式必须所有列(包括隐藏列)的偏移长度都逆序进行记录,因此其名字是Redundant(冗余的)。
另外,其存储的是字段与记录开始位置的偏移长度,不如变长列表直接存储长度直观。
另外,Redundant因为所有的字段的偏移长度都记录了,也不用担心Null值导致记录的位置对不起的问题,所以没有Null值列表。
其记录头信息如下,黄色是较Compact多的属性信息。它也没有Record_type
属性。了解即可。
4.区、段和碎片区
4.1 为什么要有区
页与页是通过双向链表进行连接的,如果以页为单位分配存储空间,逻辑上相邻的两个页在物理磁盘上实际距离比较远。在进行范围查询时,如果页与页之间的距离过远,在进行磁盘I/O操作时加载页就需要花许多时间(寻道、旋转等),就是随机I/O。磁盘和内存的速度相差了好几个数量级,磁盘随机I/O会花大量时间在数据页的查找加载上,是非常慢的。
出于性能考虑,我们希望能够在相邻的位置存取数据,以便能够顺序读取数据页。当然,我们也不能够让所有数据都存储到连续的空间,毕竟越大的连续空间在磁盘上越难找到。因此我们引入了区的概念。一个区就是64个在物理空间上连续的页。因此一个区的大小是16KB * 64 = 1MB。在表的数据量大的时候,我们就不再以页为单位进行存储空间的分配了,而是连续分配一个区,甚至是多个区。
4.2 为什么要有段
一个区中存放的页可能是数据页或者目录页,但当我们进行范围查询时,感兴趣的只有普通数据页。如果因为存放了目录页的原因,导致范围查找的效果大打折扣。我们希望一个区存储数据页就存储数据页,存储目录页就只存储目录页,因此出现了段的概念。常见的段有数据段、索引段、回滚段。段是逻辑上的概念。由若干零散的页(碎片区中的页,下一节介绍)和完整的区所组成。
4.3 为什么要有碎片区
InnoDB存储引擎一个聚簇索引会生成两个段,数据段和索引段,而每个段是以区作为单位申请存储空间的,如果表的数据量只有几条,也需要申请2M的空间么(一个区的大小为1M)?而且每增加一个索引,又需要增加2M,这空间浪费的也太严重了。
因此提出了碎片区,在一个碎片区中,可以让多个段共用一个公共空间,一些页给段A,一些页给段B,让空间得到充分的利用。就好比一个大广场可以给体育生大篮球,也可以给大妈跳广场舞,但它不是任何人所独有,而是一块公共空间。
现在我们可以知道InnoDB存储引擎分配存储空间的具体策略了。
在刚向表中插入数据时,先使用碎片区以页为单位进行存储空间的分配。
当某个段占用的存储空间达到了32个碎片区以后,我们再以完整的区为单位分配存储空间。
4.4 区的分类
区大体可以分成四个类型。
空闲区(Free):现在还没有用到这个区的任何页面。
有剩余空间的碎片区(Free-Frag):表示碎片区中还有可用的页面。
没有剩余空间的碎片区(Full-Frag)。
附属于某个段的区(FSEG):专属于给某一个段使用的完整的区。
前三种类型的区都是独立的,直属于表空间。FSEG是属于段的。
5.表空间
表空间是一个逻辑上的容器。可以划分为:独立表空间,系统表空间,撤销表空间,临时表空间等。
5.1 独立表空间
每个表对应一个表空间,也就是一个表的索引和数据会被单独保存在自己的表空间中,可以在不同的数据库之间进行数据的迁移。
其空间回收也比较方便,可以通过Drop Table操作自动回收表空间。对于统计分析或者日志表,还可以在删除大量数据之后,通过alter table tableName engine=innodb回收不用的空间。这个特性使碎片空间不会太影响性能。
独立表空间的结构由段、区、页等组成,不再赘述。
一个新建表的.idb文件大小是96kb,也就是6个页面大小(Mysql5.7),当然随着数据量的增加,有些idb也是自扩展的,表空间的文件大小会变大。在mysql5.6后,默认使用的都是独立表空间。可使用下面的语句查询。
5.2 系统表空间
与独立表空间的结构基本类似,整个MySQL进程只有一个表空间,会额外记录一些关于整个系统的数据,这是独立表空间中所没有的。比如在information_schema
中提供了以INNODB_SYS
开头的一些表,用于帮助我们查看与数据库系统相关的信息(相当于数据字典的副本)。