内存和磁盘每次交互都是完整的页,数据页里面存放的是行(不仅仅是数据库的数据行,还有行格式等)
页(16k,计算机与内存的最小单位)的上层单位还有区(一个区存放64个页,64*16k = 1024k,刚好 1M),区上面是段(一个或多个区组成),段上面是表空间(一个或多个段组成)
行格式
show table status like 't_user\G'
查看 t_user 表的行格式- 创建表的时候可以指定行格式,
create table (...) row_format = compact
- Compact:紧凑型,默认
- 变长字段列表:比如 varchar(255) 存姓名 '张三',其实只是用了 6 个字节长度。这个会记录下来
- null 值列表:可以为 null 并且实际也是 null 的字段列表。比如一条数据是:1, null, null, 13,null 什么都不是存不了,可以用一个特殊的符号来表示,但是每个 null 都用一个特殊的符号来代替,不节省空间,所以把 null 专门标识出来,在真实数据中就不存 null 列的值了。有 非空 约束或者主键等就会忽略,如果一个表中没有可以为空的字段,那么这个表的行格式中就没有 null 值列表
- 真实数据:1,数据库的行;2:row_id,3:事务ID,4:回滚指针(2,3,4 都是隐藏列)
- 记录头信息:里面有 delete_mask(是否删除),next_record(下一条数据指向)等等
- Dynamic(动态型),Compressed(压缩型)
- 表字段 varchar 最长是 65535,如果真的这样去设置字段长度约束的话会报错,因为还要存别的信息(两个字节存储变长字段,1个字节存储 null 标识,所以一个字段长度最多只能设置为 65532。如果设置非空约束,那么极限长度是 65533)
- 一个数据页16k,16*1024=16384 个字节,如果一个 varchar 字段长度设置为 65532,这个数据页肯定存放不下,这就是行溢出。行溢出的时候,该字段只存储部分长度(和别的页中剩下的内容地址),别的字节存储在别的页中
- Dynamic,Compressed,Compact 的行格式的区别就是行溢出的时候不同
- Redudant:冗余型,这是 MySQL5.0 之前 的格式。没有 null 值列表,冗余了 null
页
- 文件头和文件尾(File Header,File Trailer)
- File Header:当前页编号,下一页/上一页指针(维护页的双向链表),校验和等信息
- 文件头和文件尾都有校验和,头和尾的校验和是一致的,数据才是完整的。比如数据在同步的时候,刷盘(内存数据写入磁盘)或者集群同步的时候,页的数据是从上往下写入,头的校验和等于尾的校验和才算一致,这个页才算同步成功。有可能刷盘的途中宕机,数据页的头和尾的的校验和就可能不相等
- 文件头和文件尾也都有 LSN 值,作用和校验和相同,也是校验数据页是否完整
- 页头和页目录(Page Header,Page Directory)
- B+Tree 聚簇索引中目录页记录的不是每个主键,而是最大的主键(这个就是Page Directory存储的)
- Page Header:本页的记录数量(包括删除的),当前页在所引树的层级等等
- 用户记录,最小最大记录和空闲空间(User Records,Infimum/Supermum,Free space)
- User Records:放的是数据库的一行行数据(按照行格式存放),数据是单链表。行格式作用之一就是用来维护这个单链表的;行格式作用之二是怎么把数据库行存在到页上(弄清楚行格式的每个属性就能知道数据究竟是怎么存放的,垃圾链表,可重用空间)
- Free space:总大小就这么多,用户记录占用空间越大,空闲空间就越小
- Infimum/Supermum:最小指向数据库数据行的最小记录,最大的指向最大(这俩不是数据库的记录,数据记录的链表是行格式来维护,别搞混了)
区
- 既然内存与磁盘交互的最小单位是页,索引也是这样存的,为什么还需要区的概念呢?
- 数据页是连续的,单向链表,效率始终没有数组快(链表逻辑上是连续的,内存中不是连续的),如果数据页能连续就好了,但是这是达不到的,不可能内存总是连续的
- 不能保证每个数据页是连续的,一块内存连续这个是可以做到的,这就是区的作用
- 总结就是某些区的数据页是连续的(顺序I/O,随机I/O)
- 区也是双向链表
段
- 叶子节点和非叶子节点分开存储(一个索引会有两个段)
- 数据都在叶子节点上,如果没有段的概念要范围查找,有可能会到不同的区才能查到
- 叶子节点叫数据段,目录页节点(非叶子节点)叫索引段。还有回滚段(回滚段没在索引上)等
碎片区
-
属于表空间不属于段,完整的区才属于段
-
一个索引会有两个段,每个段至少一个区,一个区至少 1M
-
如果一个表只有几条数据,也会至少占用 2M 内存(因为一定会有聚簇索引),如果还创建别的索引就太浪费空间了
-
碎片区就是存储零散的数据,不同的数据页共用一个区
表空间
- 最上层存储结构,所有数据都在表空间中
- 可以分为系统表空间,独立标空间,撤销表空间,临时表空间
- 独立表空间(5.6版本之后默认)
- 一个表独占的表空间
- 默认 96k,6 个数据页(MySQL8.0是 112 k,会多一个页,因为8.0把 .frm 和 .idb 文件二合一了),随着数据增大空间也会增大
- 系统表空间
- 存放系统相关的东西,比如数据字典
- 数据字典:比如插入数据的时候如果某个字段有非空约束,我们给这个字段设置为空时就会报错,就是因为先查询系统表空间发现不满足插入条件而抛出的
空间分配过程
- 刚开始插入数据的时候,肯定会创建索引,索引就肯定会有两个段
- 初始占用空间很小,不会单独分配一个区,而是使用碎片区(直属于表空间)
- 当某个段已经占用 32 个碎片区之后,就会申请完整的区来存储