存储结构¶
存储的行为是由存储引擎实现的,不同的存储引擎实现不同,这里只讲 InnoDB。
默认情况下, MySQL 的数据库文件存放在 /var/lib/mysql
目录下,每个数据库对应一个目录,目录名称就是数据库名称。
目录下存放着数据库的表文件,表文件的后缀名是 .frm
,存放表的结构信息。表的数据存放在 .ibd
文件中。
Example
以上是 user_center
数据库下的 userinfo
表:
db.opt
存放数据库的一些配置信息,例如数据库的默认字符集和字符校验规则等等。userinfo.frm
存放表的结构信息,MySQL 中每建立一张表都会生成一个.frm
文件,主要包含表结构定义等信息。userinfo.ibd
存放表的数。表数据既可以存放在共享表空间(文件名: ibdata1)中,也可以存放在独立的表空间中(文件名: 表名.ibd), 这个由innodb_file_per_table
参数决定,1 则使用独占表空间,0 则使用共享表空间。从 MySQL 5.6.6 开始,innodb_file_per_table
默认为 1。
除了这些文件,还有一些其他文件,例如 ibdata1
、ib_logfile0
、ib_logfile1
、ib_logfile2
等等,这些文件是 InnoDB 存储引擎的文件,用来存放 InnoDB 存储引擎的数据和日志信息。
表空间文件结构¶
一共有这些单位:表空间(tablespace)、段(Segment)、区(Extent)、页(Page)、行(Row)。
表空间(tablespace)由段(Segment)构成,段由区(Extent)构成,区由页(Page)构成,页由行(Row)构成,页是存储的最小单位。
行 row¶
数据库表中的记录都是按行存放的,每行记录根据不同的行格式有不同的存储结构。
页 page¶
记录是按照行存储的,但是数据库不会以「行」为单位,否则一次读取(一次 I/O)只能处理一行的数据,效率很低。
所以 InnoDB 的数据是按照「页」为单位来读取的,即当我们要读一条记录的时候,这一页的数据都将被读到内存中,默认每个页的大小为 16KB。
页是 InnoDB 磁盘管理的最小单元,所以每次读写都是以 16KB 为单位,一次最少读 16KB 的内容到内存,一次最少把内存中的 16KB 内容刷新到磁盘。
页的类型有很多,常见的有 数据页、UNDO 日志页、溢出页 等。表中的行记录是存储在「数据页」的。
Tip
- 数据页(Data Page): 存储表中的行记录。
- UNDO日志页(Undo Log Page): 记录事务执行前的数据,用于事务回滚。
- 溢出页(Overflow Page): 用于存储大字段数据,当一个行的数据大小超过一个页的大小时,会把超出的部分存储到溢出页中。
区 extent¶
InnoDB 存储引擎是用 B+树来组织数据的。B+树中的每一层都是通过双向链表连接起来的。
如果以页为单位来分配空间,那么相邻的两个页之间的物理位置不一定是连续的,会导致磁盘查询时有大量的随机 IO,效率很低。
所以 InnoDB 会把连续的页组织成一个区,这样可以减少磁盘的随机 IO,提高查询效率。
在表中数据量大的时候,为某个索引分配空间的时候就不在按照页(Page)为单位分配了,而是按照区(Extent)为单位分配,一个区包含多个页。
每个区的大小默认为 1MB,对于 16KB 的页,一个区就包含 64 个页。
段 segment¶
段是由区组成的,一个段可以包含多个区,段是 InnoDB 存储引擎管理空间的逻辑单位。 一般分为 数据段、索引段、回滚段。
Tip
- 数据段(Data Segment): 存放 B + 树的叶子节点的区的集合。
- 索引段(Index Segment): 存放 B + 树的非叶子节点的区的集合。
- 回滚段(Rollback Segment): 存放的是回滚数据的区的集合,用于事务回滚。
小结¶
Note
按行存储、按页读取、按区分配、按段分类,这就是 InnoDB 存储引擎的存储结构。
InnoDB 行格式¶
行格式 Row Format,就是一条记录的存储结构。
InnoDB 提供了 4 种行格式,分别是:
- Redundant: 不是紧凑的
- Compact: 紧凑的
- Dynamic: 动态的
- Compressed: 压缩的
Dynamic 和 Compressed 这两个和 Compact 一样,都是紧凑的,只是存储的方式不同。
MySQL 5.7 之前的版本默认的行格式是 Compact,MySQL 5.7 之后默认的行格式是 Dynamic。
Compact 行格式¶
Compact 行格式分为前半部分:这一行的额外信息
,后半部分:这一行的实际数据
。
额外信息部分是为了描述这条记录而添加的一些信息,包括:
- 变长字段的长度列表
- NULL 值的位图
- 记录头信息
变长字段的长度列表¶
MySQL 支持一些变长的类型,如 varchar(n)、text、blob 等。这些变长类型占用两部分存储空间:真正的数据内容 和 长度(占用的字节数)。
这个长度就记录在额外信息的变长字段的长度列表中。每一行的 varchar 字段都可能是不一样的,所以每一行的 varchar 字段的长度都不一样。
Example
name
和 description
字段都是 varchar 类型的,所以每一行的这两个字段的长度都是不固定的,需要额外的空间来存储这两个字段的长度。
(为了方便理解,这里用 ascii 字符集)
现在插入几条数据:
id | name | phone | description |
---|---|---|---|
1 | a | 13800000000 | xyz |
2 | ab | 13900000000 | NULL |
3 | NULL | 13700000000 | NULL |
-
第一条记录:
列
name
的值为 a,长度 1 字节,十六进制是 0x01;列
description
的值为 xyz,长度 3 字节,十六进制是 0x03;列 id 和 phone 为固定长度,这里不用管。
所以这一列的长度列表为 「0x03, 0x01」。
-
第二条记录:
列
name
的值为 ab,长度 2 字节,十六进制是 0x02;列
description
的值为 NULL,NULL 是不会存放在真实数据里的,长度列表中不需要保存这个字段的长度。这一列的长度列表为 「0x02」。
-
第三条记录:
列
name
和列description
的值为 NULL。这一列的长度列表为 「」。