【MySQL】InnoDB存储引擎的行结构

news2024/12/25 9:29:46

文章目录

    • 前言
    • 1、MySQL的体系结构
    • 2、InnoDB逻辑存储结构
    • 3、InnoDB记录行结构
      • 3.1、概述
      • 3.2、语法操作
      • 3.3、Compact行格式
        • 3.3.1、示意图
        • 3.3.2、记录的额外信息
        • 3.3.3、记录的真实数据
        • 3.3.4、定长字段补充
      • 3.4、行溢出

前言

MySQL服务器上负责对表中数据的读取和写入工作的部分是存储引擎,而服务器又支持不同类型的存储引擎,如InnoDBMyISAMMemory。不同的存储引擎一般是由不同的人为实现不同的特性而开发的,真实数据在不同存储引擎中存放的格式一般是不同的。

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式。存储引擎是基于表而不是基于库的,所以存储引擎也可被称为表类型。

MySQL5.5.5版本开始,InnoDB便成为了MySQL默认的存储引擎,之前版本默认的存储引擎为MyISAM

引用的优秀资源
  • 书籍:《MySQL 是怎样运行的:从根儿上理解 MySQL》——小孩子4919
  • 文章:MySQL InnoDB存储引擎的行结构——南星
  • 视频:黑马程序员 MySQL数据库入门到精通

1、MySQL的体系结构

大体来说,MySQL可以分为Server层和存储引擎层两部分(在参考书籍中将其分成了三部分,分别为:连接管理、解析与优化、存储引擎,而在本文中则是将前两个结合在一起统称为Server层)。

Server层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

存储引擎层负责数据的读取和写入,为可插拔存储引擎,除了已支持的InnoDB、MyISAM、Memory等多个存储引擎,还可在原有的基础上进行拓展。

image-20230104230939043

2、InnoDB逻辑存储结构

InnoDB是一个将表中的数据存储到磁盘上的存储引擎,而真正处理数据的过程是发生在内存中的,所以在处理数据的时候需要把磁盘中的数据加载到内存中。如果是处理写入或修改请求的话,还需要把内存中的内容刷新到磁盘上。当我们想从表中获取某些记录时,InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB。也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。

image-20230104232028386

3、InnoDB记录行结构

3.1、概述

InnoDB存储引擎是面向行的,也就是说数据是按行进行存放的,按行存放的数据便是一条条的记录,这些记录在磁盘上的存放方式也被称为行格式或者记录格式。目前InnoDB中共有4中不同类型的行格式,分别为CompactRedundantDynamicCompressed行格式。

3.2、语法操作

创建表时指定行格式

CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称;
-- 举个例子
CREATE TABLE record_format_demo (
    c1 VARCHAR(10),
    c2 VARCHAR(10) NOT NULL,
    c3 CHAR(10),
    c4 VARCHAR(10)
) CHARSET=ascii ROW_FORMAT=Redundant;

修改表的行格式

ALTER TABLE 表名 ROW_FORMAT=行格式名称;
-- 举个例子
ALTER TABLE record_format_demo ROW_FORMAT = COMPACT;

现在所创建的表record_format_demo行格式为Compact,另外,我们还显式指定了这个表的字符集为ascii,我们现在向这个表中插入两条记录:

INSERT INTO record_format_demo(c1, c2, c3, c4) VALUES('aaaa', 'bbb', 'cc', 'd'), ('eeee', 'fff', NULL, NULL);
SELECT * FROM record_format_demo;
+------+-----+------+------+
| c1   | c2  | c3   | c4   |
+------+-----+------+------+
| aaaa | bbb | cc   | d    |
| eeee | fff | NULL | NULL |
+------+-----+------+------+

3.3、Compact行格式

3.3.1、示意图

在这里插入图片描述

从上图中可以看出来,Compact行格式中一条完整的记录其实可以被分为以下两大部分:

  • 记录的额外信息:这部分信息是服务器为了描述这条记录而不得不额外添加的一些信息,这些额外信息分为3类:
    • 变长字段列表:每列的长度用1或2个字节;
    • NULL值列表:整数个字节;
    • 记录头信息:5个字节。
  • 记录的真实数据:这里除了每一条记录中我们插入的数据,还有一些隐藏的字段数据:
    • DB_ROW_ID:非必须值,用于充当行ID,唯一标识一条记录,即主键,也可称为row_id
    • DB_TRX_ID:必需值,事务ID,也可称为transaction_id
    • DB_ROLL_PTR:必需值,回滚指针,也可称为roll_pointer

针对于上面两条记录,用16进制来表示如下图所示:

根据示意图,将数据表record_format_demo中的两条记录用16进制表示如下如所示:

image-20230104234939502

3.3.2、记录的额外信息

变长字段长度列表

在MySQL中支持一些变长的数据类型,如常见的VARCHARTEXTBLOG等,我们将这些变长的数据类型指定的列称之为变长字段,这些字段中的数据长度是不固定的。而在Compact行格式中,把所有变长字段的真实数据占用的字节长度都存放在记录的开头部位,从而形成一个变长字段长度列表,各变长字段数据占用的字节数按照列的顺序**逆序**存放。

以第一条记录为例:

  • c1的类型是VARCHAR,因此该列为变长字段,真实数据为aaaa,长度为4,转换成16进制为0x04
  • c2的类型是VARCHAR,因此该列为变长字段,真实数据为bbb,长度为3,转换成16进制为0x03
  • c3的类型是CHAR,不符合变长字段的要求,因此在变长字段长度列表中不会存储该字段中的真实数据的字节长度;
  • c4的类型是VARCHAR,因此该列为变长字段,真实数据为d,长度为1,转换成16进制为0x01

摒弃不符合要求的c3列,列的顺序为c4 c2 c1,按照变长字段长度列表中需要逆序的要求,最终第一条记录的变长字段长度列表中的字节串用十六进制表示的效果为(各个字节之间实际上没有空格,用空格隔开只是方便理解):

01 03 04

另外需要注意的一点是,变长字段长度列表中只存储值为 非NULL 的列内容占用的长度,值为 NULL 的列的长度是不储存的 。

以第二条记录为例:

  • c1的类型是VARCHAR,因此该列为变长字段,真实数据为eeee,长度为4,转换成16进制为0x04
  • c2的类型是VARCHAR,因此该列为变长字段,真实数据为fff,长度为3,转换成16进制为0x03
  • c3值为NULL,不纳入考虑范围;
  • c4值为NULL,不纳入考虑范围。

摒弃不符合要求的c3c4列,列的顺序为c2 c1,按照变长字段长度列表中需要逆序的要求,最终第一条记录的变长字段长度列表中的字节串用十六进制表示的效果为(各个字节之间实际上没有空格,用空格隔开只是方便理解):

03 04
NULL值列表

在表中某些列可能存储NULL值(如果没有NOT NULL约束),这些NULL值在Compact行格式中会被存放至NULL值列表中统一管理,处理过程分别为:

  • 统计表中允许存储NULL的列;
  • 如果表中没有允许存储NULL的列,则不存在NULL值列表,否则将每个允许存储NULL的列对应一个二进制位:
    • 二进制位按照列的顺序逆序排列;
    • 二进制位个数用整数个字节的位表示,如果使用的二进制位个数不是整数个字节,则在字节的高位补0
    • 二进制位的值为1时,代表该列的值为NULL
    • 二进制位的值为0时,代表该列的值不为NULL

针对于表record_format_demo,只用c2列存在NOT NULL约束,因此允许存储NULL的列为c1 c3 c4

以第一条记录为例:

  • 因为共有3列允许存储NULL,因此对应着3位二进制位
  • 此记录中没有NULL值,按照列的顺序逆序排列为000,正确表示如下:
整数个字节的位表示:00000000
16进制表示:0x00

以第二条记录为例:

  • 因为共有3列允许存储NULL,因此对应着3位二进制位
  • 此记录中c3 c4值皆为NULL,按照列的顺序逆序排列为011,正确表示如下:
整数个字节的位表示:00000110
16进制表示:0x06
记录头信息

用于描述记录,由固定的5个字节组成。5个字节也就是40个二进制位,不同的位代表不同的意思,如图:

img

这些二进制位代表的详细信息如下表:

名称大小(单位:bit)描述
预留位11没有使用
预留位21没有使用
delete_mask1标记该记录是否被删除
min_rec_mask1B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned4表示当前记录拥有的记录数
heap_no13表示当前记录在记录堆的位置信息
record_type3表示当前记录的类型,0表示普通记录,1表示B+树非叶子节点记录,2表示最小记录,3表示最大记录
next_record16表示下一条记录的相对位置

3.3.3、记录的真实数据

MySQL会为每个记录默认的添加一些列(也称为隐藏列),具体的列如下:

列名是否必须占用空间描述
row_id6字节行ID,唯一标识一条记录
transaction_id6字节事务ID
roll_pointer7字节回滚指针

这里需要提一下InnoDB表对主键的生成策略:

  • 优先使用用户自定义主键作为主键
  • 如果用户没有定义主键,则选取一个Unique键作为主键
  • 如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为row_id的隐藏列作为主键。
    通过上述可以看出:InnoDB存储引擎会为每条记录都添加 transaction_idroll_pointer 这两个列,但是 row_id 是可选的(在没有自定义主键以及Unique键的情况下才会添加该列)。

而表record_format_demo并没有定义主键,也没有Unique键,因此在该表中三个隐藏列都会被添加上。

image-20230104234939502

3.3.4、定长字段补充

不知道各位有没有发现上面讨论的都是变长字段类型,那么定长字段类型的怎么办呢?

这又得让焦点定格在表record_format_demo上了,在创建该表时,我们显式的将表字符集设定成了ascii,这是一个定长的字符集。当我们将的字符集修改成utf8时,结果就会不一样了。

我们以**CHAR数据类型为例:对于 CHAR(M) 类型的列来说,当列采用的是定长字符集时,该列占用的字节数不会被加到变长字段长度列表,而如果采用变长字符集时,该列占用的字节数也会被加到**变长字段长度列表。这是因为:

  • 两个定长遇到一起时,最终字段的数据肯定是定长的
  • 一个定长一个变长遇到一起时,由于有一个变长的不确定因素,最终字段的数据长度为不确定,因此会被加到变长字段长度列表中;
  • 两个变长遇到一起时,最终字段的数据肯定是变长的。

值得注意的是,对于定长字段,不管有没有存放值都会占用指定字节的大小,这是因为在更新该列的值的字节长度大于原有值的字节长度而小于10个字节时,可以在该记录处直接更新,而不是在存储空间中重新分配一个新的记录空间,导致原有的记录空间成为所谓的碎片。

3.4、行溢出

MySQL 对一条记录占用的最大储存空间是有限制的,除了 BLOBTEXT 类型之外,其他所有列 (不包括隐藏列和记录头信息) 占用的字节长度不能超过 65535 个字节,当记录长度超过限制时,MySQL 会建议使用 TEXTBLOB 类型。

我们先分析一下每一行的数据占用情况,以VARCHAR类型为例:

  • 如果允许存在NULL,那么将会有额外信息的三个内容的数据长度都需要加上去;
  • 如果存在NOT NULL约束,那么将不会存在NULL值列表,但需要加上其他两个内容的数据长度。

总而言之,65535的长度并不是单指我们存放进去的数据,还得加上一些MySQL给我们自动添加上的数据。

与此同时,一个页的大小一般是16kb,转换成字节数为16384,而一个VARCHAR类型的列最多可以存储65532个字节,这样就可能造成一个页存放不了一条记录的尴尬场景。

Compact行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址(当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从而可以找到剩余数据所在的页。

以上分析的场景便是我们需要介绍的行溢出:一个页一般是16KB,当记录中的数据太多,当前页放不下的时候,会把多余的数据存储到其他页中。

img

从上图中可以看出来,对于Compact行格式来说,如果某一列中的数据非常多的话,在本记录的真实数据处只会存储该列的前768个字节的数据和一个指向其他页的地址,然后把剩下的数据存放到其他页中,这个过程也叫做行溢出,存储超出768字节的那些页面也被称为溢出页

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/146983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【博学谷学习记录超强总结,用心分享|产品经理基础总结和感悟14】

TOC 第三章, 内容服务平台优化思考 前文中,我们粗略分析了用户阅读内容的诉求和创作者编制内容的诉求,本章笔者尝试结合用户诉求分析平台的内容分发策略,结合创作者诉求分析创作者后台设计思路,创作者后台分析主要从…

Java实现支付宝支付,内网穿透,支付功能实现-57

一:支付业务 1.1 支付宝业务简介 1)网页跳转到支付宝收银台页面。用户可以使用支付宝App扫一扫屏幕二维码,待手机提示付款后选择支付工具输入密码即可完成支付; 2)如果不使用手机支付,也可以点击上图右侧…

Visual Studio解决方案中添加src文件夹的方法

如标题,这个听起来是一个愚蠢的问题,但如果在Visual Studio想添加一个src文件夹,而命名空间不包含src的话,似乎还真不容易。 如果在Visual Studio里新建一个空白解决方案,然后右键解决方案->添加->新建解决方案…

鲲鹏BigData Pro解决方案中Hive组件的部署

1 介绍 本博文基于《鲲鹏Bigdata pro之Hive集群部署》的实验手册进行,目的是方便Hive学习者重用(从本文复制)相关的指令、配置和代码。同时,会对相关的Bash命令解释,达到增进理解的目的。 2 Hive组件的部署 部署的前…

探索SpringMVC-HandlerAdapter之RequestMappingHandlerAdapter-参数解析

前言 上回,我们大概讲了下HandlerAdapter。今天带大家来认识一下,我们最常用的RequestMappingHandlerAdapter。不过只能给大家先开个头,讲下参数解析。 RequestMappingHandlerAdapter 在介绍HandlerAdapter时,我们就知道Handle…

自己动手写编译器:从NFA到DFA

上一节我们完成了使用NFA来识别字符串的功能。NFA有个问题就是其状态节点太多,使用起来效率不够好。本节我们介绍一种叫“子集构造”的算法,将拥有多个节点的NFA转化为DFA。在上一节我们描述的epsilon闭包操作可以看到,实际上所有由epsilon边…

「ARM32」MMU和页表的映射过程详解

在ARM32中,MMU主要完成虚拟地址到物理地址的映射,并且能够控制内存的访问权限,而页表是实现上述功能的主要手段。页表又分为一级页表、二级页表,在ARM64中甚至还有三级页表。为了便于理解,本章主要讲述一级页表完成段映…

centos7 安装Mysql详细教程

centos7 弃用了Mysql,默认安装了MariaDB,MariaDB是Mysql一个分支,所以要想在centos上安装Mysql,需要先进行卸载MariaDB,避免冲突 本次教程所用环境: 腾讯云服务器:centos7Mysql5.7 1. 卸载Ma…

dubbo源码实践-protocol层-invoker理解

1概述Invoker官方解释:Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个…

Python 机器学习最常打交道的 27 款工具包

为了大家能够对人工智能常用的 Python 库有一个初步的了解,以选择能够满足自己需求的库进行学习,对目前较为常见的人工智能库进行简要全面的介绍。 1、Numpy NumPy(Numerical Python)是 Python的一个扩展程序库,支持大量的维度数组与矩阵运算…

Maix Bit(K210)保姆级入门上手教程

Maix Bit(K210)快速上手 这是K210快速上手系列文章,主要内容是,设备连接,环境准备,运行第一个程序 阅读文章前提:python基础,K210是使用Micropython脚本语法的,因此需要一些python…

RocketMQ5.0.0部署与实例

一、Idea调试1.相关配置文件在E:\rocketmq创建conf、logs、store三个文件夹。从RocketMQ distribution部署目录中将broker.conf、logback_namesrv.xml、logback_broker.xml文件复制到conf目录。如下图所示。其中logback_namesrv.xml、logback_broker.xml分别是NameServer、Brok…

纯C语言实现动态爱心(详解,初学者也能看懂)

文章目录✍动态爱心实现💖一段小故事:爱心函数的由来🎈 创建动态爱心的准备(非小白可以跳过)1.爱心字符2.对easyx库里面的基础函数的认识①initgraph函数②settextcolor、settextstyle、setbkmode、outtextxy四种函数③…

PostgresSQL数据库的使用

PostgresSQL数据库的使用 下载安装 数据类型 使用指导 数据库操作 连接控制台 psql -h <实例连接地址> -U <用户名> -p <端口号>参数描述实例连接地址RDS PostgreSQL实例的连接地址&#xff0c;本机可用localhost或者127.0.0.1用户名创建的RDS Postgre…

ES语法扩展

剩余参数 剩余参数本质 // 剩余参数的本质const add(x,y,...args)>{console.log(x,y,args);}add();add(1);add(1,2);add(1,2,3,4,5); 剩余参数的注意事项 箭头函数的参数部分即使只有一个剩余参数&#xff0c;也不能省略圆括号使用剩余参数替代arguments获取实际参数剩余…

4.Isaac Jetson Nano 入门

Isaac Jetson Nano 入门 本节介绍如何在 Jetson Nano 设备上运行 Isaac SDK 示例应用程序。 有关如何开始使用 Nano 的一般说明&#xff0c;请参阅 Jetson Nano 开发工具包入门。 文章目录Isaac Jetson Nano 入门获取 IP 地址在 Jetson Nano 上运行示例应用程序PingOpenCV 边缘…

Pytorch CIFAR10图像分类 EfficientNet v1篇

Pytorch CIFAR10图像分类 EfficientNet v1篇 文章目录Pytorch CIFAR10图像分类 EfficientNet v1篇4. 定义网络&#xff08;EfficientNet&#xff09;EfficientNet介绍EfficientNet性能比较EfficientNet的baselineEfficientNet模型混合缩放方法其他版本的EfficientNet(B1-B7)判断…

错题 3jxn (8253复杂)

A 指示型指令 C 比如 ,跟C语言的return 一样&#xff0c;可以由多条&#xff0c;但是返回的位置都是一个地方 JN NEXT RET NEXT: RET A 可以重复 EQU不可以重复 C 中断向量&#xff1a;中断服务程序的入口地址 向量中断&#xff1a;识别中断你的方法 接口 编程题&#xff…

Redis关键知识点总结

Reference: http://redis.cn用处缓存数据库分布式锁&#xff08;Redission的redlock&#xff0c;自定义的lock等&#xff09;过滤器&#xff08;布隆过滤器/增强的带计数的布隆过滤器/布谷鸟过滤器等&#xff09;大规模的计算辅助&#xff08;bitmap&#xff09;消息订阅/监听 …

PyQt5入门学习(一)【小白入门系列】

PyQt5入门学习 介绍&#xff1a;PyQt5是Python较好的图形库&#xff0c;与C的Qt不同的是PyQt5封装得较为简单&#xff0c;上手也更加的方便。下面话不多说&#xff0c;开始学习PyQt5吧&#xff01; 安装过程 安装方法有两种&#xff0c;一种是下载PyQt5最新源码进行编译安装…