本文转载自InnoDB 的记录结构和页结构

概述

InnoDB将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,中页的大小一般为16KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。

MySQL 里共有四种行格式:

  • Compact
  • Redundant
  • Dynamic
  • Compressed

指定行格式的方法

CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称

ALTER TABLE 表名 ROW_FORMAT=行格式名称

COMPACT 行格式

记录的额外信息分为3类:分别是变长字段长度列表NULL值列表记录头信息

变长字段长度列表

变长字段长度列表是用来存储VARCHARTEXT等存储的字节数不确定的数据的长度。各变长字段数据占用的字节数按照列的顺序逆序存放,是逆序存放

至于记录长度时用 1 个字节还是用 2 个字节,InnoDB 有一套规则。

  • W:该字符集一个字符需要的字节数。
  • M:该类型最多能存多少个字符。
  • L:实际存储占用的字符数。

用 1 个字节还是用 2 个字节的规则是:

if M × W <= 255 {
用一个字节
} else {
if L <= 127 {
用一个字节
} else {
用两个字节
}
}

总结一下:如果该可变字段允许存储的最大字节数超过 255 字节并且真实存储的字节数超过 127 字节,使用 1 个字节,否则使用 2 个字节。

另外,长度表只保存非NULL的长度,值为NULL的长度是不存储的。

NULL 值表

用一位标记一个字段是否为 NULL,是 1 代表 NULL,0 代表 非BULL,逆序存放。整体以字节为最小单位,不足的高位补0。

记录头信息

  • 两个预留位,没有使用。
  • delete_mask:删除标记
  • min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记
  • n_owned:当前记录拥有的记录数
  • heap_no:当前记录在记录堆的位置信息
  • record_type:当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录。(记录的大小就是主键的大小)
  • next_record:下一条记录的相对位置

记录的真实数据

除了真实的列数据以外,还有隐藏列

  • row_id:行唯一标识ID(非必须)
  • transaction_id:事物ID
  • roll_pointer:回滚指针

真正列名称其实是:DBROWID、DBTRXID、DBROLLPTR

CHAR(M) 列的存储格式

由于字段长度列表存的是边长字段长度列表,所以当修改字段类型时这个列表的长度也会发生变化。以及在修改字符集时,也会导致长度列表的长度发生变化。由此会导致重新分配空间,在原有的存储空间产生碎片化。

Redundant 行格式

Redundant行格式是MySQL 5.0之前用的一种行格式,已经非常陈旧了,看看就好。

字段长度偏移列表

Compact格式记录的是长度可变的字段的长度,而Redundant记录的是所有字段的偏移量,一样是逆序存放。相邻两个字段偏移量的差就是该字段的长度。

记录头信息

  • 两个预留位,没有使用。
  • delete_mask:删除标记
  • min_rec_mask:B+树的每层非叶子节点中的最小记录都会添加该标记
  • n_owned:当前记录拥有的记录数
  • heap_no:当前记录在页面堆的位置信息
  • n_field:当前记录中列的数量
  • 1byte_offs_flag:标记字段长度偏移列表中的偏移量是使用1字节还是2字节表示的
  • next_record:表示下一条记录的相对位置

Compact相比有两处不同:

  • 多了n_field1byte_offs_flag
  • 少了record_type

由于记录中没有 NULL 值表,记录 NULL 值的策略如下:

  • 如果 NULL 的字段是长度不可变类型,直接用 0x00 填充。
  • 如果 NULL 的字段是可变长度类型,那么在字段长度偏移列表中这个字段不占长度,即这个字段的偏移量和下一个字段的偏移量相同,真实数据区域不会存任何东西。

除了以上几点,和Compact的格式大致还是相同的。

CHAR(M) 列的存储格式

Redundant格式没有Compact格式的碎片化问题。

行溢出数据

一条记录除BLOBTEXT类型之外的列(不包括隐藏列和记录头信息),占用的字节数长度之和不能超过65535字节。这65535字节包含:

  • 真实数据
  • 真实数据的长度
  • NULL 值标记,如果是 NOT NULL 则可以不存在

CompactReduntant格式中,真实记录数据的位置只会存储一部分数据,剩下的数据会存放在其他页中,并在真实记录数据的位置留下20字节的数据指向下一个页以及在下一个页的长度。

新的那些页称为溢出页

行溢出的临界点

MySQL规定每个页最少存放两条数据(原因后面再说),所以临界值是:

  • 每个记录需要的额外信息是27字节

    • 2个字节用于存储真实数据的长度
    • 1个字节用于存储列是否是NULL值
    • 5个字节大小的头信息
    • 6个字节的row_id列
    • 6个字节的transaction_id列
    • 7个字节的roll_pointer列
  • 页额外的信息共136个字节

所以只有一个列的表,行溢出的临界值是

136 + 2×(27 + n) > 16384
=> n > 8098

这个值本身没什么意义,只要有个概念就好了。

Dynamic 格式

MySQL 5.7默认的行格式就是Dynamic,它和Compact仅仅是在行溢出时有区别。

在发生行溢出时,不会存放那768个字节,而是把所有的字节都放到其他页去,只记录地址。

Compressed 格式

逻辑和Dynamic一致,但增加了压缩算法,对页进行压缩,节省空间。

CHAR(M)中 M 过大的情况

以上四种格式都会把过长的字段当做变长字段看待。

InnoDB 页结构

页是InnoDB的存储空间的基本单位,大小一般是16K

InnoDB有许多不同的页:

  • 表空间头部信息的页
  • Insert Buffer 的页
  • INODE 信息的页
  • undo 日志的页
  • ......

以下内容针对索引(INDEX)页展开。

页结构快速浏览

(图中的单位疑似错误,应为字节)

大致的结构为:

  • File Header:文件头部
  • Page Header:页头部
  • Infimum + Supremum:最小和最小两个虚拟行记录
  • User Records:用户记录
  • Free Space:空闲空间
  • Page Directory:页面目录
  • File Trailer:文件结尾

当页刚被创建出来时,User Records是空的,每插入一条记录,就会从Free Space划过来一片空间存储,当Free Space用完时

记录和记录之间没有空隙,写在一起。

回头仔细看看记录的头信息:

  • 当记录被删除时,delete_mask被标记为 1 代表这个记录已经被删除了,这些被删除的记录会组成一个垃圾链表,称为可重用空间。后续插入时会直接覆盖。 > 将这个delete_mask位设置为1和将被删除的记录加入到垃圾链表中其实是两个阶段,后面细说。
  • min_rec_mask:每层非叶子节点中的最小记录都会添加该标记,后面细说。
  • n_owned:当前记录拥有的记录数,稍后细说。
  • heap_no:该记录在本页中的位置。InnoDB给表插入了两个虚拟记录,因此用户的记录是从 2 开始的。这两条记录没有被放在User Records部分,而是单独放在Infimum + Supremum部分。

  • record_type:0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录。
  • next_record:当前记录到下一条记录的真实数据开始的位置的偏移量,所以这玩意就是个链表。而链表的头尾就是最小记录和最大记录。当记录被删除后,类似链表删除,链表的指针会直接绕过这个节点。指针指到真实数据开始的位置,向左读取是头信息,向右读是真实数据,把变长字段长度列表和 NULL 值表放在最前面的原因也在此。这样读取更高效,无需解析各个字段的长度。

Page Directory(页目录)

根据主键查询记录时,按照记录的链表顺序查肯定是低效的。InnoDB会制作该页的目录,制作的步骤:

  • 将所有正常的记录(包括最大和最小记录,不包括标记为已删除的记录)划分为几个组。
  • 每个组的最后一条记录(也就是组内最大的那条记录)的头信息中的n_owned属性表示该记录拥有多少条记录,也就是该组内共有几条记录。
  • 将每个组的最后一条记录的地址偏移量单独提取出来按顺序存储到靠近页的尾部的地方,这个地方就是所谓的页目录。页目录中的这些地址偏移量被称为槽(Slot),所以页目录就是由槽构成的。

InnoDB规定,最小记录所在的组只能有1条记录,最大记录所在的组的记录数在1~8条之间,剩下的在4~8条之间。分组步骤:

  • 初始情况下一个数据页里只有最小记录和最大记录两条记录,它们分属于两个分组。
  • 之后每插入一条记录,都会从页目录中找到主键值比本记录的主键值大并且差值最小的槽,然后把该槽对应的记录的n_owned值加1,表示本组内又添加了一条记录,直到该组中的记录数等于8个
  • 在一个组中的记录数等于8个后再插入一条记录时,会将组中的记录拆分成两个组,一个组中4条记录,另一个5条记录。这个过程会在页目录中新增一个槽来记录这个新增分组中最大的那条记录的偏移量。

有了页目录,查询时只要使用二分法先找到这个主键所在的组,然后再遍历这个组的几条数据就可以了。

Page Header(页头)

页头用来记录本页中存储了多少条记录,第一条记录在哪,页目录中存储了多少个槽等等。

  • 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:一个方向连续插入的记录数量。假设连续几次插入新记录的方向都是一致的,InnoDB会把沿着同一个方向插入记录的条数记下来,这个条数就用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:页的校验和
  • FIL_PAGE_OFFSET:页号。InnoDB里每个页单独一个页号,可以通过页号唯一确定一个页。
  • 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:页属于哪个表空间

(️的字段表示到目前为止需要知道的,剩下的后面再看)

File Trailer(文件结尾)

文件结尾用来校验数据完整性,共8个字节

  • 前4个字节代表页校验和,页面被修改时,校验和会被重新计算,写磁盘时头部的校验和会先被写进去,完全写完时尾部的校验和应与头部的相同,否则就代表写的过程中有错误。
  • 后4个字节代表页面被最后修改时对应的日志序列位置(LSN),这里暂时先不管是什么。

InnoDB 的记录结构和页结构的更多相关文章

  1. 【Mysql】InnoDB 引擎中的数据页结构

    InnoDB 是 mysql 的默认引擎,也是我们最常用的,所以基于 InnoDB,学习页结构.而学习页结构,是为了更好的学习索引. 一.页的简介 页是 InnoDB 管理存储空间的基本单位,一个页的 ...

  2. Mysql之InnoDB行格式、数据页结构

    Mysql架构图 存储引擎负责对表中的数据的进行读取和写入,常用的存储引擎有InnoDB.MyISAM.Memory等,不同的存储引擎有自己的特性,数据在不同存储引擎中存放的格式也是不同的,比如Mem ...

  3. InnoDB的数据页结构

    页是InnoDB存储引擎管理数据库的最小磁盘单位.页类型为B-tree node的页,存放的即是表中行的实际数据了. InnoDB数据页由以下七个部分组成,如图所示: File Header(文件头) ...

  4. InnoDB存储引擎介绍-(7) Innodb数据页结构

    数据页结构 File Header 总共38 Bytes,记录页的头信息 名称 大小(Bytes) 描述 FIL_PAGE_SPACE 4 该页的checksum值 FIL_PAGE_OFFSET 4 ...

  5. InnoDB数据页结构

    前言 ​ 关于数据库我们知道是通过内存对磁盘进行操作的,也知道数据会落实到磁盘上,但是数据在磁盘上的存储结构可能大家还不是很清楚. ​ MySQL服务器上负责对表中的数据的读取和写入的工作的部分是存储 ...

  6. MySQL InnoDB 索引 (INDEX) 页结构

    MySQL InnoDB 索引 (INDEX) 页结构 InnoDB 为了不同的目的而设计了不同类型的页,我们把用于存放记录的页叫做索引页 索引页内容 索引页分为以下部分: File Header:表 ...

  7. 数据页结构 .InnoDb行格式、以及索引底层原理分析

    局部性原理 局部性原理是指CPU访问存储器时,无论是存取指令还是存取数据,所访问的存储单元都趋于聚集在一个较小的连续区域中. 首先要明白局部性原理能解决的是什么问题,也就是主存容量远远比缓存大, CP ...

  8. InnoDB行记录格式(compact)、InnoDB数据页结构

    1. compact 行记录格式: 变长字段长度列表,null标志位,记录头信息,列1数据,列2数据 …… 记录头信息中包含许多信息,只列举一部分: 名称 大小 描述 deleted_flag 1bi ...

  9. INNODB 数据页结构

    InnoDB DataPage 16384B 16K 38B FILE HEADER 56B PAGE HEADER RECORD Infimum + supremum Records UserRec ...

随机推荐

  1. c++面试笔试集锦

    1. char *const p 是指针常量,通俗的解释:指针本身是一个常量 也就是不能改变该指针的指向性,可以改变指向的量的值 const char *p 是常量指针,解释:指向常量的指针,指针指向 ...

  2. Redis 实战 —— 13. 扩展 Redis

    简介 当数据量增大或者读写请求增多后,一台 Redis 服务器可能没办法再存储所有数据或者处理所有读写请求,那么就需要对 Redis 进行扩展,保证 Redis 在能存储所有数据对情况下,同时能正常处 ...

  3. Effective Java读书笔记--创建和销毁对象

    1.优先考虑用静态工厂方法代替构造器2.遇到多个构造器参数时要考虑使用构建器Builder解决参数过多,不可变类型.私有构造方法,静态类的构造方法提供必要参数,剩下可选.new xxx.build() ...

  4. Java数组模拟队列 + 优化

    队列介绍 队列是一个有序列表,可以用数组或是链表来实现. 遵循先入先出的原则. 即:先存入队列的数据,要先取出.后存入的要后取出 示意图:(使用数组模拟队列示意图)  数组模拟队列 队列本身是有序列表 ...

  5. MySQL的安装、改密及远程连接

    一.下载MySQL压缩包后的安装步骤 将压缩包解压到指定的目录 编辑好配置文件 [mysql] #设置MySQL客户端默认字符集 default-character-set=utf8 [mysqld] ...

  6. git从安装到多账户操作一套搞定(一)入门使用

    作者:良知犹存 转载授权以及围观:欢迎添加微信:Allen-Iverson-me-LYN 总述     GIT是当今热门代码管理技术,但是如此火的系统,竟然是大神林纳斯花了两周用C写出来的一个分布式版 ...

  7. Educational Codeforces Round 91 (Rated for Div. 2) A. Three Indices

    题目链接:https://codeforces.com/contest/1380/problem/A 题意 给出一个大小为 $n$ 的排列,找出是否有三个元素满足 $p_i < p_j\ and ...

  8. SPOJ - PHRASES Relevant Phrases of Annihilation

    传送门:SPOJ - PHRASES(后缀数组+二分) 题意:给你n个字符串,找出一个最长的子串,他必须在每次字符串中都出现至少两次. 题解:被自己蠢哭...记录一下自己憨憨的操作,还一度质疑评测鸡( ...

  9. hdu1313 Round and Round We Go (大数乘法)

    Problem Description A cyclic number is an integer n digits in length which, when multiplied by any i ...

  10. CF1478-B. Nezzar and Lucky Number

    CF1478-B. Nezzar and Lucky Number 题意: 题目给出一个数字\(d(1\leq d \leq 9)\)代表某个人最喜欢的数字. 题目定义了幸运数字,它的含义为:若一个数 ...