MySQL 存储结构
1. MySQL数据的文件结构
MySQL的数据库文件存放在/var/lib/mysql
中,每创建一个database
就会在该目录中创建一个目录,并在该目录下保存对应的表结构和表数据。其中:
.opt
:存储当前数据库的默认字符集和字符校验规则.frm
:存储表结构定义信息.ibd
:InnoDB的索引文件,存储表数据,称之为独占表空间文件
2. 表空间文件的结构
表空间由段(segment)、区(extent)、页(page)、行(row)组成,其中表空间包含多个段,段包含多个区,区包含多个页,页包含多个行
- 行(row)
数据库表中的记录都是按行(row)进行存放的,每行记录根据不同的行格式,有不同的存储结构
- 页(page)
数据以行为单位进行存储,但以页为单位进行读取,提高IO效率。每个页默认为16KB,页的种类有很多,包括数据页、undo 日志页、溢出页等。记录是存在数据页中的。
- 区(extent)
MySQL的数据页位于B+树的叶子节点,相邻节点之间存在双向链表。遍历时,如果节点物理位置相距过远会导致效率较低。因此,当表中数据量大的时候,为索引分配空间将以区为单位取代页为单位。每个区的默认大小为1MB,存储连续的64个页,加快了顺序IO的速度。
- 段(segment)
段中除了存放数据的数据段之外,还包括存储非叶子节点的索引段和进行回滚的回滚段等。
3. InnoDB行格式
行格式(row_format)一条记录的存储结构,InnoDB提供了4中行格式:
- Redundant:5.0以前的格式,并不紧凑
- Compact:5.1-5.7的格式,更紧凑
- Dynamic和Compressed:基于Compact进行改进,5.7之后默认使用Dynamic
3.1 Compact行格式
一条Compact行格式的记录包括额外信息和真实数据两部分
3.1.1 额外信息
1. 变长字段长度列表
通过该列表,存储VARCHAR、TEXT、BLOB
等变长字段,这样读取的时候才知道对应的长度
CREATE TABLE `user`(
`id` int(11) NOT NULL,
`name` varchar(50),
`account` varchar(50),
`age` int(11),
primary key(`id`) USING BTREE
) CHARACTER SET = ascii ROW_FORMAT = COMPACT
# id, name, account, age
# -----------------------
# 1, pptg, 0000001, 11
# 2, ptg , 0000002, NULL
# 3, pp , NULL , NULL
对于上面的表和记录,ASCII编码的每个字符会占用1字节的空间,此时的变长字段长度列表如下:
此时该列表为了优化,将数据位数和数据放在一个CPU Cache中,是逆序记录的,也就是先记录account
再记录name
。对于第三条的NULL,不在该列表中记录。
注意
当行中不包括变长字段时,行格式中不会出现这个列表
2. NULL值列表
对于允许存在NULL的列,每一列按照二进制进行存放,1代表NULL,0代表不NULL,同样也是按照逆序进行记录,此外,当记录数不足8的倍数的时候,也会向上取整并在高位补0
比如上面的第个记录# 3, pp , NULL , NULL
被记录为:
# 前五位是补0
# 后三位逆序记录 age = NULL、account = NULL、name != NULL
00000110
注意
当行中全是NOT NULL时,行格式中不会出现这个列表,所以最好都设置成NOT NULL
3. 记录头信息
记录头记录了其他的信息,其中比较重要的有:
- delete_mask:标注数据是否删除,当delete数据时,并不会真正的删除记录,而是将delete_mask设置为1
- next_record:下一个记录的位置,指向的是下一个记录头和真实数据之间的位置,这样往左读就是记录头,往右读就是数据
- record_type:当前记录的类型,0是普通记录,1是B+树非叶子节点记录,2是最小记录,3是最大记录
3.1.2 数据
记录的真实数据中除了记录了字段,还有三个隐藏字段:
- row_id:隐式id,当建表时没有指明id,这里会生产隐式id,非必需,占用6字节
- trx_id:事务id,表示数据由哪个事务生成,必需,占用6字节
- roll_pointer:记录上一个版本的指针,必需,占用7个字节
MySQL规定除了TEXT、BLOB,其他所有的数据字段+变长字段长度列表+NULL值列表加在一起不能超过65535个字节,其中varchar(n)
代表的是存储n个字符 ,具体的字节数根据编码不同也不同。因此在ASCII的情况下,varchar(n)
最多能存65535 - 2 - 1
个字符
4. 行溢出
MySQL中一个页的大小是16KB,即16384字节
,但规定了最多能存储65535字节
当出现了超出页大小的部分,会触发行溢出,将多余的数据存到溢出页中。此时真实数据会保存一部分在原页中,并用20字节存储溢出页的地址,将其余的数据存储在溢出页中
Compressed和Dynamic对于行溢出的处理有所不同,它们只在原页中存储指针,将全部的数据都存储在溢出页中