内容导航
数据在不同存储引擎中存放的格式是不同的InooDb最常用
InnoDB行格式
以记录为单位往表中插入数据,在磁盘上存放的方式称为行格式目前有四种,分别是Compact,Redundant,Dynamic和Compressed
指定行格式的方法
语法如下:
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称;
#修改如下
ALTER TABLE 表名 ROW_FORMA=行格式名称;
COMPACT行格式
额外信息是为了描述该条记录而不得不添加的一些信息有以下三类
变长字段长度列表
为了支持变长的数据类型,(VARCHAR,TEXT,BLOB),必须有的东西,把所有的变长字段真实数据占用的字节数放在记录的开头,形成一个变长字段长度列表,各变长字段数据占用的字节数按逆序存放
变长字段长度可能由多个字节组成,如果是多个字节(最多两个)表示长度,如果是多字节,那么每个字节的的最高位被设置成1
NULL值列表
变长字段长度列表只存储值非NULL的列,NULL的列在NULL值列表处理
允许存储NULL的列将组成一个NULL值列表,以位图的方式指示列是否为NULL,也是逆序排列的,并且NULL值列表的长度必须整数字节,不够的位补0
记录头信息
主要是为了构建B+树存在
删除标志delete_mask
上文所述的记录格式(行格式)中,有一个delete_mask指示是否被删除,被删除的行会组成一个垃圾链表,称为可重用空间
heap_no
行格式中heap_no表示在页中的相对位置,从2开始.0和1称为伪记录,伪记录指向最大与最小记录
record_type
值 | 含义 |
---|---|
0 | 普通记录 |
1 | B+树非叶节点 |
2 | 最小记录 |
3 | 最大记录 |
next_record
类似C语言中的指针,值是下一个真实数据行相对当前行的地址偏移值(根据这个可以找到下一个next_record)(有序链表,以主键排序),在Mysql中会形成一个单链表
next_record的位置正好是头信息和真实数据的分界线,这样的好处是向左读就是头信息,向右读就是真实数据
由于next_record的特殊位置设计,变长长度和NULL值列表是倒着存的也是基于这个设计(沿next_record中心对称,读取相当方便)
n_owned
这个在下文的页部分解释
记录的真实数据
除了真实数据,MYSQL会为每个记录添加一些默认列(隐藏列)
列名 | 是否必须 | 占用空间 | 备注 |
---|---|---|---|
DB_ROW_ID | 否 | 6B | 行ID,唯一表示一条记录 |
DB_TRX_ID | 是 | 6B | 事务ID |
DB_ROLL_PRE | 是 | 7B | 回滚指针 |
DB_ROW_ID只有在没有自定义主键和unique键的情况下才会添加该列
Dynamic和Compressed行格式
类似于COMPACT行格式,不过在行溢出发生的情况下会将所有的数据都存到其他页中
行溢出
除了TEXT和BLOL类型的数据,其他的数据不能超过65535个字节(包含了所有杂项,包括可变长和是否NULL)
由于MYSQL的页面大小是16KB,当一个列的数据太多大于这个值是,Compact和Reduntant格式仅仅存储前768个字节,和一个指向其他页的地址(20B,这里包括其他页面中数据的真实占用计数),
行溢出的临界点
Mysql规定一个页中至少要存放两行记录
每个页需要136个字节存储页信息
而每个记录(MYSQL行需要额外的27个字节)
发生行溢出的临界点是 136 + 2*(n + 27) > 16KB(mysql页大小),在发生行溢出之后使用上文的处理方法
InnoDB页
InnoDB将数据划分成若干个页,以页为单位作为**磁盘和内存交换的基本单位,页的大小一半为16KB(一次最少把16KB的数据从磁盘读入内存)
存放记录的页(INDEX页),存储空间划分如下
名称 | 描述 |
---|---|
FileHeader | 页的通用信息 |
Page Header | 数据页的专有信息 |
Infimum + Supremum | 两个虚拟的行记录(最小记录和最大记录) |
User Records | 实际存储的行记录内容 |
Free Space | 页中尚未使用的空间 |
Page Directory | 页中某些数据的相对位置 |
File Trailer | 检验页是否完整 |
pageDiretory的设计
pageDeiretory(下面简称PD)的思想是采用二分算法,在每个行记录中有一个,PD存储多个槽,这些槽将行记录分为多个组,每个组多条记录,槽指向这些分组的最大记录,这些记录的n_owned字段存储改组有多少个记录排在他前面(在行记录格式部分详细阐述了Mysql有序性及其实现),然后通过这些槽的二分来查找主键
具体流程如下
每次和这些槽指向的值(永远指向分组的最大值)比较,并且缩减区间,直到high - low == 1,然后同过low来寻找值
为什么终止条件是high-low == 1,原因在于mysql的1如下规定
对于最小记录所在的分组只能有 1 条记录,最大记录所在的分组拥有的记录条数只能在 1~8 条之间,剩下的分组中记录的条数范围只能在是 4~8 条之间。
pageHeader设计
字段 | 描述和功能 |
---|---|
PAGE_N_DIR_SLOTS | 在页目录中的槽数量 |
PAGE_HEAP_TOP | 还未使用的空间最小地址,也就是说从该地址之后就是 Free Space |
PAGE_N_HEAP 本页中的记录的数量 | (包括最小和最大记录以及标记为删除的记录) |
PAGE_FREE | 第一个已经标记为删除的记录地址(各个已删除的记录通过 next_record 也会组成一个单链表,这个单链表中的记录可以被重新利用) |
PAGE_GARBAGE | 已删除记录占用的字节数 |
PAGE_LAST_INSERT | 最后插入记录的位置 |
PAGE_DIRECTION | 记录插入的方向 |
PAGE_N_DIRECTION | 一个方向连续插入的记录数量 |
PAGE_N_RECS | 该页中记录的数量(不包括最小和最大记录以及被标记为删除的记录) |
PAGE_MAX_TRX_ID | 修改当前页的最大事务ID,该值仅在二级索引中定义 |
PAGE_LEVEL | 当前页在B+树中所处的层级 |
PAGE_INDEX_ID | 索引ID,表示当前页属于哪个索引 |
PAGE_BTR_SEG_LEAF | B+树叶子段的头部信息,仅在B+树的Root页定义 |
PAGE_BTR_SEG_TOP | B+树非叶子段的头部信息,仅在B+树的Root页定义 |
File Header设计
名称 | 描述 |
---|---|
FIL_PAGE_SPACE_OR_CHKSUM | 页的校验和(checksum值) |
FIL_PAGE_OFFSET | 页号 |
FIL_PAGE_PREV | 上一个页的页号 |
FIL_PAGE_NEXT | 下一个页的页号 |
FIL_PAGE_LSN | 页面被最后修改时对应的日志序列位置(英文名是:Log Sequence Number) |
FIL_PAGE_TYPE | 该页的类型 |
FIL_PAGE_FILE_FLUSH_LSN | 仅在系统表空间的一个页中定义,代表文件至少被刷新到了对应的LSN值 |
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID | 页属于哪个表空间 |
FileTailer设计
用于检测页是否完全被同步到了磁盘里面