MySQL磁盘存储
1.行格式
我们可以对一个表指定他的行存储的格式,这里我们用一个COMPACT格式,我们可以建表的时候指定,也可以后续修改
CREATE TABLE table_name (columns) ROW_FORMAT=COMPACT
ALTER TABLE table_name ROW_FORMAT=COMPACT
在这种格式下,每一行数据实际存储的格式大致如下:
变长字段的长度列表 | null值列表 | 数据头 | column01的值 | column01的值 | column0n的值 | … |
---|
对于每一行数据,他其实存储的时候都会有一些头字段对这行数据进行一定描述,然后再放上他这一行数据每一列的具体的值,这就是所谓的行格式,除了COMPACT以外,还有其他几种存储格式,基本都大同小异
2.变长字符串存储
比如有一行数据,VARCHAR(10),VARCHAR(5),VARCHAR(20),VARCHAR(1),VARCHAR(1),一共5个字段,其中三个是变长字段,假设一行数据是这样的:hello hi hao a a,现在三个变长的字段长度分别是0x05,0x02,0x03,但是实际放在变长字段长度列表的时候是逆序放的,所以一行数据实际存储可能是:
0x03 0x02 0x05 null值列表 头字段 hello hi hao a a
3.NULL值存储
对所有的NULL值不通过字符串在磁盘上存储,而是通过二进制的bit位来存储在NULL值列表里,假设一张表:
CREATE TABLE customer (
name VARCHAR(10) NOT NULL,
address VARCHAR(20),
gender CHAR(1),
job VARCHAR(30),
school VARCHAR(50)
) ROW_FORMAT=COMPACT;
现在有如下一行数据:
name | address | gender | job | school |
---|---|---|---|---|
jack | m | xx_school |
首先变长字段的长度按照逆序放在变长字段长度列表中,NULL值在NULL值列表中体现出来,所有允许为NULL值的字段都有一个二进制bit位的值,1说明是NULL,0说明不是NULL,实际上NULL值列表也是逆序排放的,所以他的四个bit位是0101,NULL值列表存放的时候不会说仅仅是4个bit位,一般起码是8个bit位的倍数,高位补0,所以实际存放看起来如下:
0x09 | 0x04 | 00000101 | 头信息 | column01=value1 | column02=value2 | columnN=valueN | … |
---|
4.数据头的存储
数据头是用来描述这行数据的,有40个bit位,第一个bit位和第二个bit位都是预留位,没任何含义
- delete_mask,1个bit位,他标识这行数据是否被删除
- min_rec_mask,1个bit位,B+树里每一层的非叶子节点里的最小值都有这个标记
- n_owned,4个bit位,记录了一个记录数
- heap_no,13个bit位,他代表的是当前这行数据在记录堆里的位置
- record_type,3个bit位,这就是说这行数据的类型,0代表普通类型,1代表B+树非叶子节点,2代表的是最小值数据,3代表的是最大值数据
- next_record,16个bit位,这个是指向他下一条数据的指针
5.实际数据在磁盘上的存储
一行数据"jack NULL m NULL xx_school"真实存储大致如下所示:
0x09 0x04 00000101 0000000000000000000010000000000000011001 jack m xx_school
在读取这个数据的时候,它会根据变长字段的长度先读取出来jack,然后发现第二个字段是NULL,就不用读取了,第三个字段是定长字段,直接读取1个字符就可以了,第四个字段是NULL,就不用读取了,第五个字段是变长字段长度是9,读取出来xx_school就可以了
实际上字符串这些东西都是根据我们数据库指定的字符集编码,进行编码之后再存储的,大致看起来一行数据如下所示:
0x09 | 0x04 | 00000101 | 0000000000000000000010000000000000011001 | 616161 | 636320 | 6262626262 |
---|
在一行数据存储的时候,会在他真实数据的部分加入一些隐藏字段:
- DB_ROW_ID字段,这就是一个行的唯一标识,是数据库内部搞得一个标识,不是主键ID字段,如果我们没有指定主键和unique key唯一索引的时候,内存自动加一个ROW_ID作为主键
- DB_TRX_ID字段,这是跟事务相关的,事务ID
- DB_ROLL_PTR字段,这是回滚针,是用来进行事务回滚的
加上这几个隐藏字段之后,实际一行数据可能看起来如下所示:
0x09 | 0x04 | 00000101 | 0000000000000000000010000000000000011001 | 00000000094C(DB_ROW_ID) | 00000000032D(DB_TRX_ID) | EA000010078E(DB_ROL_PTR) | 616161 | 636320 | 6262626262 |
---|
6.行溢出
行溢出就是说一行数据存储的内容太多了,一个数据页都存放不下,此时只能溢出这个数据页,把数据溢出存放到其他数据页里面去,那些数据页就叫做溢出页
比如有一个表的字段类型是VARCHAR(65532),这远大于16kb的大小了,实际上数据页存储这行数据,仅仅包含它一部分的数据,同时包含一个20个字节的指针,指向了其他的一些数据页,那些数据页用链表串联起来存放这个VARCHAR(65532)超大字段里的数据
7.数据页
8.表空间
我们平时创建的表,都有一个表空间的概念,在磁盘上对应着"表名.ibd"的磁盘数据文件,所以在物理层面上,表空间就是对应一些磁盘上的数据文件,有的表空间对应多个磁盘文件,有的表空间对应一个磁盘文件
表空间的磁盘文件里有很多数据页,一个表空间的数据页太多不便于管理,又引入数据区(extent)的概念,一个数据区对应着连续64个数据页,每个数据页是16kb,所以一个数据区是1mb,然后256个数据区被划分为一组
表空间的第一组数据区的第一个数据区的前三个数据页都是固定的,存放了一些描述性的信息:
- FSP_HDR:存放了表空间和这一组数据区的一些属性
- IBUF_BITMAP:存放着一组数据页所有的insert buffer信息
- INODE:存放着一些特殊的信息
当我们执行CRUD操作的时候,就是从磁盘上的表空间数据文件里,去加载一些数据页到buffer pool的缓存页中去使用,我们插入一条数据,这个时候会看往那个表插数据,根据表找到对应的表空间,定位到对应的磁盘文件,有了磁盘文件就找一个extent区,再找一个数据页