写点什么

《MySQL 系列》 InnoDB 行记录存储结构

用户头像
Silently9527
关注
发布于: 2 小时前
《MySQL系列》 InnoDB行记录存储结构

程序员常用的 IDEA 插件:https://github.com/silently9527/Toolkit

本文已被 Github 仓库收录 https://github.com/silently9527/ProgrammerNotes


前言

我们平时在向 MySQL 数据库表中插入数据时,实际数据是以行记录的格式存储在磁盘上的,本篇我们就一起来详细的了解下 MySQL 的行记录格式,理解了行记录的格式有助于我们后面了解 MySQL 如何快速在页中定位出行记录,以及 MySQL 的版本控制链,事务隔离级别等等,行记录格式是许多 MySQL 核心知识的基础。

InnoDB 行记录类型

MySQL 中总共提供了四种类型的行格式:Compact,Redundant,Dynamic,Compressed


在创建表或修改表的时候可以指定行记录的格式create table 表名 row_format=行格式名alter table 表名 row_format=行格式名


知道就行,不需要去记住,基本上使用不到

Compact 行格式

在四种类型的行格式中,我们主要来学习Compact格式,其他格式的行记录类似;



从图中我们可以看出行记录主要是由 4 部分组成:变长字段长度、Null 值列表,行记录头信息以及列的真实数据

变长字段长度列表

在 MySQL 中很一些变长的数据类型(varchar,text 等),MySQL 需要知道这些数据的实际长度,这样才能正确的在真实数据中取出对应列的数据,所以变长字段是由两部分组成:


  • 真实数据的长度

  • 真实数据的字节


每个变长字段的长度要么用 1 字节要么用 2 字节表示,由此就决定了每个字段的最大字节数是 65535;


  • 假如字符类型若为 gbk,每个字符最多占 2 个字节,最大长度不能超过 32766;

  • 假如字符类型若为 utf8,每个字符最多占 3 个字节,最大长度不能超过 21845。


那到底什么时候选用 1 字节什么时候选用 2 字节呢?


这里需要定义三个变量:w,m,l


  1. 假如使用的字符集是 utf8mb4,每个字符占用的字节数是 4 字节,那么 w=4;假如字符类型若为 utf8,每个字符最多占 3 个字节,那么 w=3; 所以 w 表示字符集中每个字符所占的字节数

  2. varchr(m),这里 m 表示的是定义的字符的长度

  3. l 表示的是该字段真实数据占用的字节数


m*w <= 255;表示该字段定义的最大长度都不会超过 1 字节,那么该字段的长度就用 1 字节表示


m*w > 255 && l<=127; 表示该字段定义的长度可能会超过 1 个字节,但是当前的实际长度是小于 127 的,可以用 1 个字节表示


m*w > 255 && l>127; 用 2 字节来表示该字段的长度


思考:为什么与 l 比较的值是 127 呢?当我们定义的变长字段可能大于 255(也就是超过一个字节)时,MySQL 如何才能知道当前读取的字节该字段的完成字段长度,还是该字段的半个字段长度,为了解决这个问题,MySQL 使用了 1 字节的首位,当首位为 0 表示当前是 1 字节,当首位为 0 表示当前长度是 2 字节;由于占用了 1 字节的首位,所以剩下 7 位所能表示的最大值是 127


变长字段不会存储为 Null 列的长度;其次并不是行记录中一定需要变长字段长度这段内容,如果行记录中没有定义变长字段或者是变长字段都为 Null,那么就不会有变长字段长度这部分


变长字段占用的字节数按照顺序逆序存储

Null 值列表

一条记录中某些列通常可能允许为 null,所以 Compact 行格式把这些允许为 null 的进行了统一管理;


  1. 首先统计出表中定义的哪些列允许为 null

  2. 如果表中的字段都不能为空,那么就不存在 null 值列表;如果存在允许为 null 的字段,那么就按照字段的顺序为每个字段对应一个二进制位,当二进制位为 1 时表示该列值为空;当二进制位位 0 时表示该列值不为空

  3. Null 值列表必须有整数个字节来表示,所以对应没有占用的位使用 0 补位


行记录的头信息

头信息中主要包含了 6 个字段,其中 5 个字段也是在面试中经常被问到的,为了方便记忆,我们把 5 个字段对应到手的 5 根指头:


  • n_owned(拇指): 一个数据页会被分成很多个组,每组最后的一条记录该字段为 1,其他记录该字段为 0,就像分组中所有的记录的大哥;(对应拇指)

  • deleted_flag(食指): 标记该记录是被删除的;当记录被删除时不会真实删除,而是用该字段标记,并且把所有删除的记录使用链表连接起来,以后的文章会继续说到这个字段。(想象下你平时挖鼻屎是不是用的食指)

  • heap_no(中指): 表示当前记录在数据页中的相对位置(MySQL 使用该字段来表示记录位置,可以和中指对应,不可描述)

  • record_type(无名指): 表示当前记录属于哪种类型,(无名指用来带戒指的,与分类有关,可以把人分为已婚和未婚,)

  • 0 表示普通记录

  • 1 表示目录项记录,索引中非叶子结点中的数据记录都是 1

  • 2 表示 infrmum 记录,每个数据页中至少会有两条记录,其中最小记录的 record_type=2

  • 3 表示 Supremum 记录,每个数据页中至少会有两条记录,其中最大记录的 record_type=3

  • next_record(小拇指): 存放下一条记录的相对位置(当数数时,左手的小拇指数完之后就该换右手了,和 next_record 表达的意思类型)


最后一个字段 min_rec_flag : B+树中每层非叶子结点最小目录项记录该字段为 1;该字段相对于其他 5 个字段显得不那么重要,不会影响理解 B+树索引

隐藏列

除了用户自定义的数据列以外,MySQL 还会为每行记录生成 3 个隐藏列


  • row_id: 行 ID,记录的唯一标识;当用户在表中定义了主键字段就优先选择用户定义的主键,如果没有,就查找是否有定义不为 null 的唯一索引,如果有就把该列作为主键,如果没有 MySQL 就会生成一列 row_id 隐藏列作为主键

  • trx_id: 事务的 ID;该字段对于实现一致性视图和事务隔离级别至关重要,以后会详细说明

  • roll_pointer: 回滚指针,指向的是该记录的上一个版本号,MySQL 的 MVCC 主要就是通过这个字段来实现的。

溢出列

MySQL 中所有的行记录都会被存储在数据页中,每个数据页的大小是 16KB,也就是 16384 个字节;在前面我们讲过变长字段的长度可以用两个字节来表示,所以列的最大长度可以是 65535,当遇到这种极端情况时,一个数据页是存储不下这一条记录的。


Compact 行格式针对这种情况的处理方式是在真实的数据处记录该列的一部分数据(768 字节),其他多余的数据会存储到新的数据页中(溢出页),然后在该记录中使用 20 个字节存储这些数据页的地址



溢出页与溢出页之间使用的链表相连接

其他的行记录格式:

Redundant:MySQL5.0 之前的格式,直接忽略


Dynamic,CompressedCompact很像,只是在溢出列的处理有些差异,他们只会在真实数据列中使用 20 个字节存储溢出页的地址

面试题

  • char(M)定义的字段,在变长字段的长度列表中会记录该字段的长度吗?


欢迎大家在评论区留言讨论



最后(点关注,不迷路)

文中或许会存在或多或少的不足、错误之处,有建议或者意见也非常欢迎大家在评论交流。


最后,写作不易,请不要白嫖我哟,希望朋友们可以点赞评论关注三连,因为这些就是我分享的全部动力来源🙏


原文链接:https://silently9527.cn/?p=62



发布于: 2 小时前阅读数: 7
用户头像

Silently9527

关注

公众号:贝塔学JAVA 2018.05.09 加入

Simple Programmer, Make the complex simple

评论

发布
暂无评论
《MySQL系列》01 InnoDB行记录存储结构