MySQL的数据存放在哪个文件?
- 表结构的信息存放在.frm文件中,表数据存放在.ibd文件中,而.opt文件存储的是当前数据库的默认字符集和字符校验规则
- 另外,在MySQL5.6.6之后,每一张表的数据才会单独存放在一个独立表空间文件.ibd文件中,之前都是存放在一个共享表空间文件中
- 表空间的结构:由段、区、页、行组成
行:数据库的每一条记录都是按照行存放的
页:数据库的读取如果按照行来进行,则有频繁的IO操作,因此,数据库是按照“页”为单位,将页读入内存,进行读写操作的。页的类型很多,不仅有数据页,也有undo日志页、溢出页等。而表中的记录就是存储在数据页当中的
区:B+树的每一层上的数据页都是通过双向链表连接起来的,但是他们只是逻辑上相邻,实际物理上并不相邻,这样就会造成我们在进行磁盘查询的时候,会有很多的随机IO,而随机IO是很耗时的。因此,我们在进行索引空间的分配的时候,就不会按照“页”来分配,而是按照“区”来进行分配。而区,是64个连续的数据页组成的,而每页是16KB,也就是区的大小是1MB。
段:段是由区构成的。分为三种,索引段、数据段和回滚段
索引段是存储b+树上的非叶子节点的区的集合
数据段是存储b+树上叶子节点的区的集合
回滚段是存储回滚数据的区的集合,MVCC就是通过回滚段实现了多版本查询数据
行格式有哪些?
行格式有四种: Redundant、Compact、Dynamic和 Compressed
Redundant比较古老了,它的缺点就是不够紧凑,因此已经被淘汰了。
为了让数据页能够存储更多的行记录,MySQL又对行格式进行了改进
Compact、Dynamic和 Compressed 就是紧凑的行格式
MySQL5.7之后,默认使用Dynamic行格式
重点了解一下Compact行格式,其它两种和他很像
Compact行格式
Compcat行格式分为记录的额外信息和记录的真实数据两个部分
记录的额外信息
变长字段长度列表
当数据库表中有变长字段时,则记录的额外信息中会有一个变长字段长度列表。会以十六进制的格式,按照列的顺序逆序存放每个变长字段的长度。
Tip:变长字段不仅包括varchar,还有text、blob等
比如上图中的第一条记录对应的行格式中的变长字段长度列表为:03 01
第二天记录对应的行格式中的变长字段长度列表为:04 02
- 而对于为null的变长字段,由于null是不会存放在行格式中记录的真实数据部分里的,因此变长字段长度列表中不保存值为null的字段的长度,因此第三条记录的行格式如下图所示
- 为什么变长字段长度列表要按照列顺序逆序存放呢?
因为记录头信息中指向下一条记录的指针指向的是下一条记录的记录头信息和真实数据之间的位置,这样,向左扫描记录的额外信息,向右扫描记录的真实数据。而变长字段长度列表逆序存放数据,便使得位置靠前的记录的真实数据和数据对应的字段长度信息可以同时在一个 CPU Cache Line 中,这样就可以提高 CPU Cache 的命中率。
(可以参考如何写出让 CPU 跑得更快的代码?)
Null值列表
- 以整数个字节的二进制为来表示Null值列表,按照列的顺序逆序排列(和变长字段长度列表逆序排列的原理一致),不足整数个字节时高位补0即可
- 1表示为Null,0表示不为Null
还是以上面的表为例子
- 第2条记录对应的Null值列表为
Null值列表用十六进制表示是0x04
这样一来,三条记录对应的行格式如下图
- 并不是所有数据库表的行格式都有Null值列表。当数据库表中的所有字段都定义成Not Null时,就没有Null值列表。因此在设计数据库表时,建议字段都设计成Not Null,这样可以至少节省1字节的空间。
记录头信息
记录头信息包含的内容很多,比较重要的三个:
- delete_mask:它用来标识记录是否被删除。我们执行delete语句时,记录并不是真的被删除,而是被做了标记。比如由0记为1
- next_record:指向下一条记录的位置。指向的是下一条记录的记录头信息和真实数据之间的位置,目的是方便扫描记录的额外信息和真实数据
- record_type:表示当前记录的类型。0表示普通记录;1表示B+树非叶子节点记录;2表示最小记录;3表示最大记录
记录的真实数据
三个隐藏字段
- row_id
如果创建表的时候没有指定主键或唯一约束列,那么InnoDB就会为记录创建row_id隐藏字段,占用6字节
- trx_id
事务id,表示该记录是由哪一个数据生成的,占用6字节
- roll_pointer
指向上一个版本的指针,占用7字节
varchar(n)中的n最大取值为多少?
MySQL规定,在一行记录中,处理text、blobs这种大对象类型之外,其它所有列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节
我们存储varchar(n)的数据时,是分为三个部分来存储的,也就是
** “所有真实数据占用的字节” + “变长字段长度列表占用的字节”+ “Null值列表占用字节”(定义所有字段为Not Null时可以忽略)<= 65535字节**
Tip:需要注意,varchar(n)中的n代表最多存储n个字符,采用不同字符集时,一个字符占用的字节数不同,比如采用ascii码字符集,1个字符占用一个字节。也就是采用不同字符集,varchar(n)中n的最大值取值不同
- 变长字段长度列表占用字节 = 所有变长字段长度占用字节数之和
变长字段长度占用字节数的计算:如果变长字段允许存储的最大字节数小于等于 255 字节,就会用 1 字节表示「变长字段长度」;大于255字节,则用2字节表示「变长字段长度」
- 单字段情况下,一个varchar(65532) 变长字段+ 2字节变长字段长度列表(因为变长字段长度65532 > 255)+ 1字节Null值列表(允许null)
因此一个varchar类型的列最多可以存储65533个字节(如果定义该字段Not Null则可以忽略1字节的Null值列表)
行溢出后,MySQL是如何处理的?
记录行是存放在数据页中的,一个数据页的大小是16KB,也就是16384字节,而一个varchar(n)类型的列最多可以存储65533个字节,一些大对象如blob、text可能存储更大的数据,这时一个数据页可能存不下一行记录,就会发生行溢出,溢出的数据就会存放在溢出页中。
Compact行格式下,会将一部分数据存放在真实数据的位置,再用20字节存储指向溢出页的地址
- Compressed 和 Dynamic 这两个行格式和 Compact 非常类似,主要的区别在于处理行溢出数据时有些区别。
这两种行格式采用完全的行溢出方式,记录的真实数据处不会存储该列的一部分数据,只用20字节存储指向溢出页的地址。数据都在溢出页中