列的类型/行格式/row_format
行格式种类
- compact
- redundant
- dynamic
- compressed
compact
compact格式的组成,变长字段长度列表+null值字段列表+记录头数据+各列数据。 变长字段长度列表为逆序排放,next_record指向的位置是真实数据的起点,向左读是记录头,向右读是数据,所以变长字段长度列表和null值字段列表都是逆序排放。 注意char(m)类型虽然是定长的,但是如果使用了变长的字符集则也会被变长字段长度列表记录。 默认占位为m x 字符集单字符最小占位,需要时再扩容。 记录头的组成,共40位(5字节)
- 两位预留位(2)
- 删除标记(1)
- B+树每层非叶子结点中最小的目录项纪录标记(作用于目录项记录),min_rec_flag(1)
- 组纪录数,n_owned(4)
- 当前行页面堆相对位置,heap_no(13)
- 记录类型,record_type(3)
- 0 普通用户记录
- 1 目录项记录 (包含列:最小key值,页号)
- 2 infimum
- 3 supremum
- 下一条记录相对位置(16)
默认列
- row_id 缺失主键和非空唯一索引时出现 (6字节)
- 事物Id (6字节)
- 回滚指针 (7字节)
redundant
mysql5.0之前就在使用相对古老 格式: 字段长度偏移列表+记录头信息+各列值 字段长度偏移列表记录了所有字段(包括隐藏字段)的偏移量,同样为逆序排放。 记录头信息共48位
- 保留位(2)
- 删除标记(1)
- min_rec_flag (1)
- n_owned(4)
- heap_no(13)
- 记录中列的数量 n_field (10)
- 字段长度偏移列表单位长度(1字节或2字节),1byte_offs_flag(1)
- 下一条记录绝对位置,next_record(16) null值通过字段长度偏移列表各列偏移量的第一位来表示。 char(m)类型 默认占位为m x 字符集单字符最大占位。
溢出列
记录会分配到叶中存储,叶一般为16k如果存不下则会产生溢出。 compact和redundant会存储溢出列一部分内容在行记录中,然后将其他部分存放到其他叶,再记录这些叶的地址。共20个字节,20个字节里包括叶的地址和数据的字节数。mysql中一叶知少存储2行记录。 132+2x(27+n)<16384,n为数据真实占用的字节数。满足这个公式则不会产生溢出列。n<8099
Dynamic和Compressed
dynamic是5.7默认使用的格式。 Dynamic和Compressed与Compact格式大致相同,不同点在于溢出列的存储。它们不在记录溢出列数据的前一部分数据(前768字节),而只记录指向溢出叶的地址和字节数(20个字节),Compressed的不同点在于它会使用压缩算法压缩数据节省空间。
索引页
索引页就是存放真实数据的叶,索引页的组成
- 文件头 File Header 38B
- 叶头 Page Header 56B
- 最大最小记录 Supermun&Infimum 26B
- 用户记录 User Record
- 空闲空间 Free Space
- 叶目录 Page Directory
- 文件尾 File Trailer 8B 38+56+26+8=128B User Record 紧密存放着行记录数据,被称为堆,记录头里的hape_no指的就是记录在叶的相对位置。并且这些记录通过记录头中的next_record把所有记录组成一个链表。链表头就是Infimum,链表尾就是Supermum。被标记为删除的数据不会在链表中。这个链表中按照主键值大小排列,Infimum和Supermum没有主键值。
Page Directory 是为了在一个索引页内有很多条记录时方便查询的设计,他把所有记录分成若干组,记录头中n_owned就是标记的该组有多少条记录,这个标记会在每组的末尾出现,Infimum的n_owned值为1。叶目录中的这些地址偏移量称为槽(Slot),每个槽占用2个字节。叶目录就是由多个槽组成的。 Supermum的n_owned值在1~8之间,其他组记录数在4~8之间。
页头主要存放页内的数据情况,最后插入的位置,插入方向,数据总数,有效数据数等。 文件头主要页对于整个系统的信息,比如上一页,下一页位置,页的类型,页的归属,LSN,校验和 页的类型: 文件尾主要用于保证数据正确性,存放的校验和和LSN尾号以与文件头做对比
表空间
表空间被划分为多个连续的区,默认一个区有64个页,每256个区被划为一组,每组前几个页的类型是固定的,段是一个逻辑概念,段是由零散的直属页和完整的区组成。 每个区都有对应的一个XDES entry(Extend Descriptor Entry)用来标记
- 空闲区
- 有空闲页的碎片区
- 没有空闲页的碎片区
- 对于附属于某个段的区,同一个段下面的区会被组织成
- FREE链表 空闲区链表
- NOT_FULL链表 还有空闲页的区的链表
- FULL链表 没有空闲页的区的链表
一个区有64个页,16K一个页 一个区就是1M。一个区中64个页的叶号是连续的,物理空间也是连续的。每256个区 (extent)为一组,第一组前三个page类型固定,其余组前两个page类型固定。
一个索引会产生两个段,叶子结点段、非叶子结点段。叶子结点和非叶子结点会放在不同的区,不会混放。存放叶子结点的区的集合算一个段(segment),非叶子结点同理,一个索引有两个段。
碎片区(fragment extend)中的叶可以属于多个段也可不属于任何段,碎片区直属于表空间。 刚开始插入数据时,段从某个碎片区中申请单个页,当某个段占用32个碎片区的叶的时候以完整的区为单位申请空间。原先占用的碎片区的页不会复制到新申请的区中。