定义
- 库中记录展示时是一条记录一条记录的,也就是一行一行
- 那这样的每条记录在库中的存储方式称为行格式
- 行格式归属于存储引擎范围(换个存储引擎可能就不同方式存储)
分类
Compact (
Version 5.6 默认)
Redundant (
比较老的数据格式,mysql 5.0 以前使用)
Dynamic (
Version 5.7 默认)
Compressed(
不能应用在System data,时间换空间)
在compact行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的前768个字节的数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址,从而可以找到剩余数据所在的页(这就是行溢出)。dynamic中会直接在真实数据区记录 20字节 的溢出页地址, 而不再去额外记录一部分的数据了。(compact和dynamic的区别主要在行溢出的处理)
compressed 格式将会在Dynamic 的基础上面进行压缩处理特别是对溢出页的压缩处理,存储在其中的行数据会以zlib的算法进行压缩,因此对于blob、text这类大长度类型的数据能够进行非常有效的存储。但compressed格式其实也是以时间换空间,性能并不友好,并不推荐在常见的业务中使用。
Compact和Dynamic应用较广泛;
规定(只讨论InnoDB):
- 存储单元为页(page),16KB(16384B)
- 每页至少需要存两条数据
- 每条记录都会有记录头
- 记录几乎都会有3个隐藏列(rowId,transactionId,rollPointer)
;下面通过Compact
行格式的实现来初步看看MySQL
的记录存储智慧。
变长字段(记录的长度)列表 + NULL列表 + 记录头信息 + 列值
一、变长字段长度列表
几个点了解一下:
- 何谓变长?就是不定长。不定长从两个点理解: 字符集与属性类型
- 变长类型示例:
varchar
、varbinary
、text
、blob
- 这些变长列的实际占用字节数以
逆序
方式存储在变长字段长度列表
中 - 允许的最大字节超过255且实际存储超过127字节, 使用两个字节存储其长度, 否则使用一个字节.(看到有多节省了吗...)
- 变长列不为
null
时,InnoDB
才会存储其字节长度 - 如果没有变长列或变长列都为null,则当前记录
没有
此部分 - 第一个字节的第一位是标志位,表示是否双字节表示.(有点像
gbk
哈)
这里注意最后一点, InnoDB在读字段变长列表时会先查表结构, 允许的最大字节数超过255时才会使用这个二进制位作为标识位来判断是读一个字节还是两个字节,没有超过就直接读一个字节也就不存在标识位了
举个栗子
列 col
类型为varchar(255)
- 如果字符集为
ascii
,col
列允许的最大字节为 255 * 1 = 255个字节,说明撑死了2^8 - 1个字节,一个字节存其长度就够了; - 如果字符集为
utf8
,col
允许的最大字节为 255 * 3 = 765个字节,大于255,那就要看实际占用字节数了, 一个不够就两个
问题:Compact行格式下,页中某列最大字节长度是多少?(可以先想想)
答案:不要忘了记录是存在页里面的,每页一般16KB,也就是65536个字节,16次方不就占两个字节吗~
当然有可能一列超过16KB,那就会溢出了(溢出页
),这里只记录当前页的占用字节数。
二、NULL值列表
同样几个点了解一下:
- 主键列、
not null
修饰的列不在此范围内 - 如果没有可以省略此部分。(即除了主键列全是
not null
修饰) - 一个列一个二进制位,1表示
null
- 同样
逆序
存放 - 此部分由整数个字节组成, 不足地方高位补0
这就不举栗子了,被我吃完了。
三、记录头信息
记录头:"上面的两位都是弟弟。"
Compact行格式下记录头各部分示意图(来源网络)
名称 | 大小(单位:bit) | 描述 |
---|---|---|
预留位1 | 1 | 没有使用 |
预留位2 | 1 | 没有使用 |
delete_mask | 1 | 标记该记录是否被删除 |
min_rec_mask | 1 | B+树的每层非叶子节点中的最小记录都会添加该标记 |
n_owned | 4 | 表示当前记录拥有的记录数 |
heap_no | 13 | 表示当前记录在记录堆的位置信息 |
record_type | 3 | 表示当前记录的类型,0表示普通记录 ,1表示B+树非叶子节点记录 ,2表示最小记录 ,3表示最大记录 |
next_record | 16 | 表示下一条记录的相对位置 |
这里一堆属性,我们挑几个看看
delete_mask
- 被删除的记录值为1, 正常记录为0
delete
语句后的记录不会被立刻删除,而是将这条记录的delete_mask
置1, 称为delete mask
操作,与真正的直接删除要区分开(update不更新主键且不能就地更新
时直接删除,也就是改完delete_mask
后直接加入到垃圾链表中),这种操作是因为MySQL
还有一个东东叫MVCC
,后面会讲。- 这个属性还涉及垃圾链表、重用空间什么的
min_rec_mask
- 这个属性目前我也不知道是干啥的,哪位大佬知道的可以告诉我一声~
n_owned
- 在页面内为了快速搜索(二分查找)会分组
- 只有组内最大记录此字段有值,记录组内记录数,除了
最小记录
,大小一般在4-8区间
heap_no
- 后面我们会说到记录在页面内其实会组成一个
单链表
,从头到尾,此属性依次增加. - 最小记录为0,最大记录为1,真正记录的这个值从2开始
record_type
- 页内记录类型,目前有四种类型
- 0就是我们的一般意义上的记录,1是索引用到的,后面再说
- 2是最小记录、3 是最大记录
next_record
- 本记录的真正数据到下一条记录的真正数据的偏移量(可以当做存了个指针,向后是额外信息,向前是具体的列)
- 根据这个属性,页面内所有记录都串了一个单链表
- 单链表按主键排序,从小到大,最小记录与最大记录分别为头结点和尾节点
四、正式数据部分
额外信息部分说差不多了,到真正存储数据的地方了,喝口水再继续吧。
朋友之上恋人未满的三个特殊列:
列名 | 是否必须 | 占用空间 | 描述 |
---|---|---|---|
row_id | 否 | 6字节 | 行ID,唯一标识一条记录 |
transaction_id | 是 | 6字节 | 事务ID |
roll_pointer | 是 | 7字节 | 回滚指针 |
这三个的位置在真正数据的最开始位置, 属于MySQL
为我们添加的隐藏列,不过也不是所有的情况都添加:
-
表中没有指定主键且没有Unique列,
MySQL
会为我们添加一个row_id 作为主键 -
其他两个每条记录都会默认添加
-
trx_id
其实就是事务ID,这个是在五号表空间
的全局变量 -
roll_pointer
回滚指针,指向一条undo日志记录(在undo日志记录页
中)
- MySQL有个自增的功能,其实是维护了一个全局变量
- 这个变量存储在系统表空间的7号表(
7号表空间
)中,随着记录的插入而自增, 每逢256的倍数就刷到磁盘中- 重启系统取出后加上256再赋予给自增列, 因为库中可能已经超过了刷到磁盘中的这个值
- 最后一点, 强扭的瓜不仅不甜也不解渴, 所以没看懂就不要勉强......
到此, 每条记录的Compact格式说完了,其他行格式大家有兴趣就自己找找资料看看吧,这里不展开了。
作者:sunyelw
链接:https://www.jianshu.com/p/67fa5037ab5a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
相关:Mysql 行格式https://www.cnblogs.com/souyoulang/p/11089723.html
MySQL --MVCChttps://www.jianshu.com/p/24c85c6b34bf
关于Mysql数据存储,你了解多少? https://www.sohu.com/a/637829484_121124374