InnoDB数据存储结构

MySQL服务器上 存储引擎负责对表中数据的读取和写入工作,不同存储引擎中 存放的格式一般是不同的,甚至有的存储引擎(Memory)不用磁盘来存储数据。

  • 页 (Page) 是磁盘和内存之间交互的 基本单位,也就是说数据库管理存储空间的基本单位是页,数据库I/O操作的 最小单位是页 (InnoDB页默认大小16KB)

  • 区 (Extent) 是比页大一级的存储结构,在InnoDB存储引擎中 ,一个区会分配64个连续的页。因为InnoDB中页的默认大小为16KB,所以一个区的大小是1MB=64*16KB

  • 段 (Segment) 由一个或多个区组成,段中不要求区与区之间是相邻的。段是 数据库中的分配单位,不同类型的数据库对象以不同的段形式存在,当我们创建数据表、索引时,会创建对应的段。

  • 表空间 (Tablespace) 是一个逻辑容器,在一个表空间中可以有一个或多个段,一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间、用户表空间、撤销表空间、临时表空间等。

1_InnoDB页结构

数据库中的页可以 不在物理结构上相连,只要通过 双向链表关联即可。每个数据页中的记录会按照列 (主键或其他) 的顺序 (从小到大或者从大到小) 组成一个 单向链表,每个数据页都会为存储在页内的记录生成一个 页目录,在通过该列查找某条记录的时候可以在页目录中使用 二分法快读定位到对应的槽,然后再遍历该槽对应的分组中的记录即可快速找到指定的纪录。

InnoDB数据存储结构

1.1.File Header

File Header 文件头部 (38字节) :描述页的通用信息。

  • FIL_PAGE_OFFSET(4字节) : 页号(唯一)
  • FIL_PAGE_TYPE(2字节) : 代表当前页的类型。
  • FIL_PAGE_PREV(4字节) 和 FIL_PAGE_NEXT(4字节) : 分别代表本页的上一个和下一个页的页号。 通过建立一个双向链表把许多页都串联起来,保证这些页之间 不需要是物理上的连续,而是逻辑上的连续
  • FIL_PAGE_SPACE_OR_CHKSUM(4字节) : 当前页面的校验和(checksum)。 为了检测一个页是否完整(也就是在同步的时候有没有发生只同步一半的尴尬情况)

    什么是校验和? 就是对于一个很长的字节串来说,我们会通过某种算法来计算一个比较短的值来代表这个很长的字节串,这个比较短的值就称为校验和。 在比较两个很长的字节串之前,先比较这两个长字节串的校验和,如果校验和都不一样,则两个长字节串肯定是不同的,所以省去了直接比较两个比较长的字节串的时间损耗。 页的校验和的作用: 为了检测一个页是否完整,这时可以通过 文件尾的校验和文件头的校验和做比对,如果两个值不相等则证明页的传输有问题,需要重新进行传输,否则认为页的传输已经完成。 每当一个页面在内存中修改了,在同步之前就要把它的校验和算出来,因为File Header在页面的前边,所以校验和会被首先同步到磁盘,当完全写完时,校验和也会被写到页的尾部,如果完全同步成功,则页的首部和尾部的校验和应该是一致的。如果写了一半儿断电了,那么在File Header中的校验和就代表着已经修改过的页,而在File Trailer中的校验和代表着原先的页,二者不同则意味着同步中间出了错。

  • FIL_PAGE_LSN(8字节) : 页面被最后修改时对应的日志序列位置

1.2.File Trailer

File Trailer 文件尾部(8字节):检验页是否完整

  • 前4个字节代表页的校验和:这个部分是和File Header中的校验和相对应的。
  • 后4个字节代表页面被最后修改时对应的日志序列位置(LSN):这个部分也是为了校验页的完整性的,如果首部和尾部的LSN值校验不成功的话,就说明同步过程出现了问题。

1.3.Free Space

Free Space 空闲记录:页中还没有被使用的记录

我们自己存储的记录会按照指定的行格式存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分, 每当我们插入一条记录,都会从Free Space部分,也就是尚未使用的存储空间中申请一个记录大小的空间划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。

InnoDB数据存储结构

1.4.User Records

User Records 用户记录:存储行记录的内容

User Records中的这些记录 按照指定的行格式一条一条摆在User Records部分,相互之间形成 单链表

1.5.Infimum+Supremum

Infimum+Supremum 最小和最大记录(26字节):虚拟的行记录

记录可以比大小,对于一条完整的记录来说,比较记录的大小就是 比较主键的大小

比方说我们插入的4行记录的主键值分别是:1、2、3、4,这也就意味着这4条记录是从小到大依次递增。

InnoDB规定的最小记录与最大记录这两条记录的构造十分简单,都是由5字节大小的 记录头信息和8字节大小的一个 固定的部分组成的

这两条记录不是我们自己定义的记录,所以它们并不存放在页的User Records部分,他们被单独放在一个称为Infimum + Supremum的部分

InnoDB数据存储结构

1.6.Page Directory

Page Directory 页目录(56字节):存储用户记录的相对位置

在页中,记录是以单向链表的形式进行存储的。单向链表的特点就是插入、删除非常方便,但是检索效率不高,最差的情况下需要遍历链表上的所有节点才能完成检索。因此在页结构中专门设计了页目录这个模块, 专门给记录做一个目录,通过 二分查找法的方式进行检索,提升效率。

  • 将所有的记录 分成几个组,这些记录包括最小记录和最大记录,但不包括标记为”已删除”的记录。

    InnoDB规定:

  • 对于最小记录所在的分组只能有1条记录,
  • 最大记录所在的分组拥有的记录条数只能在1~8条之间,
  • 剩下的分组中记录的条数范围只能在是 4~8 条之间。
  • 在每个组中最后一条记录的头信息中会存储该组一共有多少条记录,作为 n_owned字段。

    n_owned字段存在于行的记录头中 n_owned(4bit):页目录中每个组中最后一条记录的头信息中会存储该组一共有多少条记录

  • 页目录用来存储 每组最后一条记录的地址偏移量,这些地址偏移量会 按照先后顺序存储起来,每组的地址偏移量也被称之为 槽(slot),每个槽相当于指针指向了不同组的最后一个记录。

分组的步骤:

  • 在初始情况下,一个数据页中只有两条记录:最小记录和最大记录,分别属于两组。
    [En]

    in the initial case, there are only two records in a data page: the minimum record and the maximum record, which belong to two groups.*

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

举例:

InnoDB数据存储结构

页目录结构下查找记录

在数据页中查找具有指定主键值的记录的过程分为两个步骤:

[En]

The process of finding a record with a specified primary key value in a data page is divided into two steps:

  1. 通过二分法确定该记录所在的槽,并找到该槽所在分组中主键值最小的那条记录。
  2. 通过记录的next_record属性遍历该槽所在的组中的各个记录。

举例:

InnoDB数据存储结构

查找记录中主键值为6的记录:

因为各个槽代表的记录的主键值都是从小到大 排序的,所以我们可以使用 二分法来进行快速查找。

5个槽的编号分别是:0、1、2、3、4,所以初始情况下最低的槽就是low=0,最高的槽就是high=4。

  1. 计算中间槽的位置:(0+4)/2=2,所以查看槽2对应记录的主键值为8,又因为8 > 6,所以设置high=2,low保持不变。
  2. 重新计算中间槽的位置:(0+2)/2=1,所以查看槽1对应的主键值为4,又因为4 < 6,所以设置low=1,high保持不变。
  3. 因为high – low的值为1,所以确定主键值为6的记录在槽2对应的组中。此刻我们需要找到槽2中主键值最小的那条记录,然后沿着单向链表遍历槽2中的记录。

通过二分法确定主键值为6的记录在槽2对应的组中,定位槽2中最小的数据

怎么定位一个组中最小的记录呢?
别忘了,每个槽是相邻的,我们可以很容易地获得槽1对应的记录(主键值为4)。该记录的下一条记录是槽2中主键值最低的记录(这些记录由单个链接表链接)。此记录的主键值为5。

[En]

Don’t forget that each slot is next to each other, and we can easily get the record corresponding to slot 1 (the primary key value is 4). The next record of this record is the record with the lowest primary key value in slot 2 (the records are linked by a single linked table). The primary key value of this record is 5.

从槽2中最小的数据 (主键值为5的记录) 出发,遍历槽2中的各条记录,直到找到主键值为6的那条记录即可。

1.7.Page Header

Page Header 页面头部(56字节):页的状态信息

为了能得到一个数据页中存储的 &#x8BB0;&#x5F55;&#x7684;&#x72B6;&#x6001;&#x4FE1;&#x606F;,比如本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等,特意在页中定义了一个叫Page Header的部分,这个部分占用固定的56个字节,专门存储各种状态信息。

2_InnoDB行格式

我们平时的数据以行为单位来向表中插入数据,这些记录在磁盘上的存放方式也被称为 &#x884C;&#x683C;&#x5F0F;或者 &#x8BB0;&#x5F55;&#x683C;&#x5F0F;。InnoDB存储引擎设计了4种不同类型的行格式,分别是 CompactRedundantDynamicCompressed行格式。

行格式操作

查看MySQL8的默认行格式:

SELECT @@innodb_default_row_format;

查看具体表的行格式:

SHOW TABLE STATUS like '&#x8868;&#x540D;';

指定行格式:

#&#x521B;&#x5EFA;&#x8868;&#x65F6;&#x6307;&#x5B9A;&#x884C;&#x683C;&#x5F0F;
CREATE TABLE &#x8868;&#x540D; (&#x5217;&#x7684;&#x4FE1;&#x606F;) ROW_FORMAT=&#x884C;&#x683C;&#x5F0F;&#x540D;&#x79F0;
#&#x4FEE;&#x6539;&#x8868;&#x7684;&#x884C;&#x683C;&#x5F0F;
ALTER TABLE &#x8868;&#x540D; ROW_FORMAT=&#x884C;&#x683C;&#x5F0F;&#x540D;&#x79F0;

2.1.compact行格式

在MySQL 5.1版本中,默认设置为Compact行格式。一条完整的记录其实可以被分为 &#x8BB0;&#x5F55;&#x7684;&#x989D;&#x5916;&#x4FE1;&#x606F;&#x8BB0;&#x5F55;&#x7684;&#x771F;&#x5B9E;&#x6570;&#x636E;两大部分。

InnoDB数据存储结构

1.变长字段长度列表

变长字段长度列表:记录所有变长字段的真实数据占用的字节长度

MySQL支持一些变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、TEXT类型,BLOB类型,这些数据类型修饰列称为 &#x53D8;&#x957F;&#x5B57;&#x6BB5;,变长字段中存储多少字节的数据不是固定的,所以我们在存储真实数据的时候需要顺便把这些数据占用的字节数也存起来。

注意:存储的变长长度和字段 &#x987A;&#x5E8F;&#x76F8;&#x53CD;

举例:

InnoDB数据存储结构

各个列都使用的是 ASCII字符集(每个字符只需要1个字节来进行编码)。

长度值以与列相反的顺序存储,因此可变长度字段长度列表的最后一个字节字符串以十六进制表示:060408

[En]

The length value is stored in reverse order of the column, so the final byte string of the variable length field length list is represented in hexadecimal: 060408

行格式:

InnoDB数据存储结构

2.NULL值列表

NULL值列表:把可以为NULL的列统一管理起来,标记对应列的值是否为NULL

注意:

  • 这里面存储的NULL值列表和字段 &#x987A;&#x5E8F;&#x76F8;&#x53CD;
  • 如果表中没有允许存储 NULL 的列,则 NULL值列表也不存在了。

为什么定义NULL值列表?

  • &#x6570;&#x636E;&#x90FD;&#x662F;&#x9700;&#x8981;&#x5BF9;&#x9F50;&#x7684;,如果没有标注出来NULL值的位置,就有可能在查询数据的时候出现混乱。
  • 如果使用一个特定的符号放到相应的数据位表示空置的话,虽然能达到效果,但是这样很浪费 &#x7A7A;&#x95F4;,所以直接就在行数据得头部开辟出一块空间专门用来记录该行数据哪些是非空数据,哪些是空数据
  • 二进制位的值为1时,代表该列的值为NULL。
  • 二进制位的值为0时,代表该列的值不为NULL。

举例:

InnoDB数据存储结构

行格式:

InnoDB数据存储结构

3.记录头信息

InnoDB数据存储结构
  • delete_mask(1bit):标记着当前记录是否被删除(1被删除,0没有删除)

    被删除的记录为什么还在页中存储呢? 你以为它删除了,可它还在真实的磁盘上。这些被删除的记录之所以不立即从磁盘上移除,是因为移除它们之后其他的记录在磁盘上需要重新排列,导致 &#x6027;&#x80FD;消耗。所以只是打一个删除标记而已,所有被删除掉的记录都会组成一个所谓的 &#x5783;&#x573E;&#x94FE;&#x8868;,在这个链表中的记录占用的空间称之为可重用空间,之后如果有新记录插入到表中的话,可能把这些被删除的记录占用的存储空间覆盖掉。

  • min_rec_mask(1bit):标识是否为最小的目录项记录(1最小记录,0不是最小记录)
  • n_owned(4bit):页目录中每个组中最后一条记录的头信息中会存储该组一共有多少条记录
  • heap_no(13bit):表示当前记录在本页中的位置

    怎么不见heap_no值为0和1的记录呢? MySQL会自动给每个页里加了两个记录,由于这两个记录并不是我们自己插入的,所以有时候也称为伪记录或者虚拟记录。这两个伪记录一个代表 &#x6700;&#x5C0F;&#x8BB0;&#x5F55;,一个代表 &#x6700;&#x5927;&#x8BB0;&#x5F55;。最小记录和最大记录的heap_no值分别是0和1,也就是说它们的位置最靠前。

  • record_type(3bit):当前记录的类型
  • 0:表示普通记录
  • 1:表示目录项记录
  • 2:表示最小记录
  • 3:表示最大记录
  • next_record(16bit):表示从当前记录的真实数据到下一条记录的真实数据的地址偏移量。

    下一条记录: 下一条记录指得并不是按照我们插入顺序的下一条记录,而是 &#x6309;&#x7167;&#x4E3B;&#x952E;&#x503C;&#x7531;&#x5C0F;&#x5230;&#x5927;&#x7684;&#x987A;&#x5E8F;的下一条记录。而且规定Infimum记录(也就是最小记录)的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录(也就是最大记录)。

4.记录的真实数据

除了我们自己定义的列的数据外,记录的实际数据还将有三个隐藏列:

[En]

The real data recorded will have three hidden columns in addition to the data of our own defined columns:

  • row_id:一个表没有手动定义主键,则会选取一个Unique键作为主键,如果连Unique键都没有定义的话,则会为表默认添加一个名为row_id的隐藏列作为主键。所以row_id是在没有自定义主键以及Unique键的情况下才会存在的。

2.2.dynamic&compressed行格式

在MySQL 8.0中,默认行格式就是Dynamic,Dynamic、Compressed行格式和Compact行格式挺像,只不过在处理行溢出数据时有分歧:

行溢出:
一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65533个字节,这样就可能出现一个页存放不了一条记录,这种现象称为行溢出。

  • Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了 &#x5B8C;&#x5168;&#x7684;&#x884C;&#x6EA2;&#x51FA;的方式。
  • Compact和Redundant两种格式会在记录的真实数据处存储一部分数据

InnoDB数据存储结构

Compressed行记录格式的另一个功能就是,存储在其中的行数据会以zlib的算法进行 &#x538B;&#x7F29;,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能够进行非常有效的存储。

2.3.redundant行格式

InnoDB数据存储结构

字段长度偏移列表与变长字段长度列表有两处不同:

  • 少了”变长”两个字:Redundant行格式会把该条记录中所有列(包括隐藏列)的长度信息都按照逆序存储到字段长度偏移列表。
  • 多了”偏移”两个字:这意味着计算列值长度的方式不像Compact行格式那么直观,它是采用两个相邻数值的差值来计算各个列值的长度。

记录头信息与Compact行格式的记录头信息对比来看,有两处不同:

  • Redundant行格式多了n_field和1byte_offs_flag这两个属性。
  • n_fields:代表一行中列的数量,占用10位
  • 1byte_offs_flags:定义了偏移列表占用的大小(1:1个字节,0:2个字节)

    1byte_offs_flag的值是怎么选择的 根据该条Redundant行格式记录的 &#x771F;&#x5B9E;&#x6570;&#x636E;&#x5360;&#x7528;&#x7684;&#x603B;&#x5927;&#x5C0F;来判断的:

    • 当记录的真实数据占用的字节数值不大于127(十六进制0x7F,二进制01111111)时,每个列对应的偏移量占用1个字节。
    • 当记录的真实数据占用的字节数大于127,但不大于32767(十六进制0x7FFF,二进制0111111111111111)时,每个列对应的偏移量占用2个字节。
    • 当记录的真实数据占用的字节数大于32767时,将部分记录存放到溢出页中,在本页中只保留前768个字节和20个字节的溢出页面地址。
  • Redundant行格式没有record_type这个属性。

Redundant行格式中NULL值的处理:

因为Redundant行格式并没有NULL值列表,所以Redundant行格式在字段长度偏移列表中的各个列对应的偏移量处做了一些特殊处理 ——&#x5C06;&#x5217;&#x5BF9;&#x5E94;&#x7684;&#x504F;&#x79FB;&#x91CF;&#x503C;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#x6BD4;&#x7279;&#x4F4D;&#x4F5C;&#x4E3A;&#x662F;&#x5426;&#x4E3A;NULL&#x7684;&#x4F9D;&#x636E;,该比特位也可以被称之为NULL比特位。也就是说在解析一条记录的某个列时,首先看一下该列对应的偏移量的NULL比特位是不是为1。如果为1,那么该列的值就是NULL,否则不是NULL。

Original: https://www.cnblogs.com/gmkun/p/mysql_innodb.html
Author: 九虑
Title: InnoDB数据存储结构

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/505426/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • windows和乌班图使用固定的ip地址

    windows设置固定的ip地址:查看网上的方法很多人说修改无线网卡的配置:自动获取ip—-》使用下面的IP地址这样修改以后无法使用wifi上外网但是确实可以添加一个固…

    数据库 2023年6月11日
    0120
  • openstack 安装neutron网络服务安装 报错:Unknown operation ‘enabled’

    注:这个脚本文件有一个地方是错误的,最后一行需要修改一下 vim /usr/local/bin/iass-install-neutron-controller-gre.sh 改sy…

    数据库 2023年6月14日
    087
  • Linux–>常用指令

    Linux的选项可以组合使用比如说ls -al这种 pwd 指令 作用:查看当前所在目录的 绝对路径 语法: pwd ls 指令 作用:显示当前目录下所有文件 语法: ls &am…

    数据库 2023年6月14日
    0175
  • springboot~Cache注解缓存在代码中的获取

    对于springboot中基于方法的缓存Caching来说,我们直接以声明的方式添加,删除这些缓存,而它们在redis这种持久化产品中,通过 value::key的方法组成一个re…

    数据库 2023年6月6日
    097
  • python自动安装mysql5.7

    python版本:python2.6 centos版本:centos6.9 mysql版本:mysql5.7.19 安装目录路径和数据目录路径都是固定,当然也可以自己修改 这个脚本…

    数据库 2023年6月9日
    0165
  • jdbc-实现用户登录业务(解决sql注入问题)

    package com.cqust; import java.sql.*;import java.util.HashMap;import java.util.Map;import …

    数据库 2023年5月24日
    069
  • 使用归并排序思想解决逆序对数量问题

    概述 归并排序算法,想必诸位都十分熟悉。其基本思想也就是 分治。整个排序过程分成两部分–分治法将问题 分(divide)成一些小的问题然后递归求解,而 治(conque…

    数据库 2023年6月11日
    096
  • 设计模式之简单工厂

    一、简单工厂:为了客户类和服务类之间的解耦,把对象的创建任务交给第三方类,这个第三方类就充当工厂的作用,严格来说简单工厂不属于23种设计模式之一。 二、实现思路 :创建一个简单工厂…

    数据库 2023年6月14日
    086
  • Spring常见问题

    Spring常见问题 问渠那得清如许?为有源头活水来。 Spring 是个 java 企业级应用的开源开发框架。Spring 主要用来开发 Java 应用,但是有些扩展是针对构建 …

    数据库 2023年6月14日
    077
  • mysql8主从配置

    一、一般配置主从(这里主是m3300,从是3301、3302) 1.配置m3301 从mysql8里拿出这两个文件到m3301 2.配置my.ini &#x521D;&am…

    数据库 2023年5月24日
    092
  • 新的开始

    今天,我正式开始我的博客分享,java人加油 posted @2022-03-05 21:25 小小羊儿 阅读(10 ) 评论() 编辑 Original: https://www…

    数据库 2023年6月11日
    095
  • B树-查找

    B树系列文章 1. B树-介绍 2. B树-查找 3. B树-插入 4. B树-删除 查找 假设有一棵3阶B树,如下图所示。 下面说明在该B树中查找 52的过程 首先,从根结点出发…

    数据库 2023年6月14日
    0139
  • postman自动化测试

    postman做接口的自动化测试case 记录一次自动化测试的工作,以及该过程中对于测试设计的一些思考。 postman工具 简单介绍,这个工具无论是开发还是测试,使用来调试接口的…

    数据库 2023年6月6日
    0120
  • 数据库概述

    MySQL的启动、停止 启动: net start mysql80 停止: net stop mysql80 (PS:mysql80为Win注册到MySQL中的系统服务名称)* M…

    数据库 2023年5月24日
    073
  • gin 使用pprof 进行性能分析

    开发中发现接口的耗时有点久,需要分析一下,之前也使用过pprof,但没有整理,又重新百度了一下,这次就记一下。 在main 文件中加入 pprof.Register(engine)…

    数据库 2023年6月9日
    089
  • Redis集群(一)主从复制

    一、主从复制概述 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由…

    数据库 2023年6月11日
    086
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球