上篇文章介绍了行溢出,表中多创建65535个字节,而null值列表占用一个字节,变长字段长度列表占用两个字节,所以长是65532个字节。而varchar(M)填写多少,要根据不同的字符集来规定,比如ascii一个字符是一个字节,gbk大是2个字节,utf8大是3个字节。数据也会溢出,数据溢出,则是会分成若干页存储,而compact行格式,真实数据列表会780左右字节,然后存页的地址值,方便查找剩余的真是数据。Mysql5.7后默认用dynamic行格式,而dynamic行格式在行溢出的情况下真实数据列表只存储页码地址值。Redundant则是会有压缩算法压缩页码分页,更节省空间。
回忆一下:
前面我们知道了查询一条数据,需要先tcp/ip先客户端链接服务端,之后会查询缓存,有的话直接返回,insert 和update都会让缓存失效,解码sql,优化sql,再访问我们现在说的存储引擎。而字符集的解码sql会发生乱码,所以用set names可以让charater_Set_client ,character_set_connection,character_set_results都设置成相同的字符集,提高效率,减少编码解码性能消耗。而存储引擎innoDB存储分为几个部分,变长字段长度列表,null值列表,头部信息列表,之后就是真实存储数据列表,当数据太多,就会分页存储,每页大概16kb。Compact和dynamic行格式不同,dynamic是当存在分页情况,是在真实数据只存指向页面的地址值,来查询数据,mysql5.7后默认的就是dynamic,行溢出和数据溢出也需要了解。
现在我们就要着重看看存放 数据的“index页”是什么?
存放我们表中记录类型的页,官方称为INDEX页(索引页),这些表中的内容就是我们日常存储的数据,所以又称为数据页。
innoDB数据页16kb大小存储空间可以划分为多个部分,不同部分有着不同的功能,
File Header:38个字节,文件头部,页的一些通用信息。
Page Header:56个字节,页面头部,数据页(index页)专有的信息。
Infimum+supremun:26个字节,小记录和大记录,两个虚拟行记录。
User records:大小不确定(看存储的数据),用户记录,实际存储的行记录内容。
Free space:大小不确定,空间空间,页中尚未使用的剩余空间。
Page directory:大小不确定,页面目录,页中某些记录对应的位子,地址值。
File Trailer:8个字节,文件尾部,效验文件是否完整。
注意:每个页中一开始并没有user records空间,是存入行数据开始,会从free space里的空间申请,分配一部分给user records存储数据,直到free space没有剩余空间,这时候就会申请新的页。
我们之前都介绍过变长字段长度列表和null值列表,唯独没有介绍头部信息列表,先回顾一下头部由什么组成?
预留位1:1bit,没有使用。
预留位2:1bit,没有使用。
Delete_mask:1bit,标记当前行是否被删除。
Min_rec_mask:1bit,B+树每层非子叶节点小记录都会添加该标记。
N_owned:4bit,表示当前记录拥有的记录数。
Heap_no:13bit,表示当前记录堆的位子信息。
Record_type:3bit,表示当前记录的类型,0代表普通类型,1代表B+树非叶节点记录,2表示小记录,3表示大记录。
Next_record:16bit,表示下一条记录的位子。
创建 一个表,指定字符集是ascii ,行格式为compact,主键指定为c1,所以当前只会有transaction_id事务id和roll_pointer回滚指针两个隐藏列,row_id隐藏列不存在,插入几条数据:
create table index_page_tb(
-> c1 int,
-> c2 int,
-> c3 varchar(1000),
-> primary key(c1)
-> )charset=ascii row_format=compact;
Query OK, 0 rows affected (0.07 sec)
//插入数据
mysql> INSERT INTO index_page_tb VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd');
Query OK, 4 rows affected (0.01 sec)
Records: 4 Duplicates: 0 Warnings: 0
插入之后存储的结果就是:
Delete_mark,min_rec_mask,n_owned,heap_no,record_type,next_record,C1,C2,C3,其他值
条记录:0,0,0,2,0,32,1,100,’aaaa’,其他值
第二条记录:0,0,0,3,0,32,2,200,’bbbb’,其他值
第三条记录:0,0,0,4,0,32,3,300,’cccc’,其他值
第四条记录:0,0,0,5,0,-111,4,400,’dddd’,其他值
上面都是前面说的 user records部分。
Delete_mark:占用一个二进制,0代表当前未删除,1代表已删除。所以在删除之后,磁盘上还是存在的,并不是数据就删除了,因为删除之后,大量的数据重排序消耗性能,而删除后的数据会组成一个垃圾链表,垃圾链表占用的空间称为可重用空间,之后有新的记录插入进来,可能吧这些删除记录占用的空间覆盖掉。(注意delete_mark标记删除 和 组成垃圾链表是两个步骤,后面在事务中会详情介绍~)
Min_rec_mask:B+树的每层非叶子节点小记录都会添加该标记,目前四条记录都是0,代表他们都不是非叶子节点小记录。
N_owned:一两句说不清,后面着重会详细介绍,稍安勿躁,铁汁们~
Heap_no:存储当前数据在当前页的索引地址,那为什么从2开始呢,0和1在哪,因为innoDB默认会给每个页自动添加两条虚拟数据,一个代表小记录(infimum)和大记录(supremum),他们比较大小的方式是按主键索引比较大小的,这两个是固定的数,所以此刻加上就变成了:
Delete_mark,min_rec_mask,n_owned,heap_no,record_type,next_record,C1,C2,C3,其他值
小记录:0,0,1,,2,28,infimum
大记录:0,0,5,1,3,0,supremum
条记录:0,0,0,2,0,32,1,100,’aaaa’,其他值
第二条记录:0,0,0,3,0,32,2,200,’bbbb’,其他值
第三条记录:0,0,0,4,0,32,3,300,’cccc’,其他值
第四条记录:0,0,0,5,0,-111,4,400,’dddd’,其他值
从加粗斜体我们可以看到heap_no的索引值对应上了。
Record_type:0代表普通记录,1代表b+树非叶子节点记录,2,小记录,3大记录。可以看到我们插入的普通数都是0,而小记录和大记录代表2和3。
Next_record:记录下一条数据位子信息,比如我们删掉第二条记录之后,第二条记录和大记录都会变成:
delete from index_page_tb where c1 = '2';
Query OK, 1 row affected (0.02 sec)
第二条记录:1,0,0,3,0,0,2,200,’bbbb’,其他值
大记录:0,0,4,1,3,0,supremum
看结果可知,delete_mark已经为1已删除状态,并且next_Record也成了0,而且大记录数的n_owned变为4。
若果insert 则会看到:
insert into index_page_tb values(2,200,'bbbb')
-> ;
Query OK, 1 row affected (0.00 sec)
数据重新回到之前空间位子,因为删除的数据会组成垃圾链表,方便以后的数据重新利用空间。
文章来源:知乎平台 原文地址:https://zhuanlan.zhihu.com/p/399532316