mysql底层数据存储原理

2023-11-13

一、前言

1.1 数据存储在哪里?

        操作系统从寄存器中读取数据是最快的,因为它离CPU最近。但是寄存器有个非常致命的问题是:它只能存储非常少量的数据,设计它的目的主要是用来暂存指令和地址,并非存储大量用户数据的。
        内存的大小虽然比寄存器大,但是还是不能保存过多的数据,而且服务器重启的话,数据就会丢失了。
        综上所述,innodb选择将数据存储在磁盘中,容量大,同时保证数据不丢失。

众所周知,在mysql5以前,默认的存储引擎是:myslam。但mysql5之后,默认的存储引擎已经变成了:innodb,它是我们建表的首选存储引擎。

二、数据页

我们可以把一批数据放在一起。

        写操作时,先将数据写到内存的某个批次中,然后再将该批次的数据一次性刷到磁盘上。如下图所示:

读操作时,从磁盘上一次读一批数据,然后加载到内存当中,以后就在内存中操作。如下图所示: 

         将内存中的数据刷到磁盘,或者将磁盘中的数据加载到内存,都是以批次为单位,这个批次就是我们常说的:数据页

        当然innodb中存在多种不同类型的页,数据页只是其中一种,我们在这里重点介绍一下数据页。

那么问题来了,什么是数据页?

        数据页主要是用来存储表中记录的,它在磁盘中是用双向链表相连的,方便查找,能够非常快速得从一个数据页,定位到另一个数据页。

很多时候,由于我们表中的数据比较多,在磁盘中可能存放在多个数据页当中。

        有一天,我们要根据某个条件查询数据时,需要从一个数据页找到另一个数据页,这时候的双向链表就派上大用场了。磁盘中各数据页的整体结构如下图所示:

         通常情况下,单个数据页默认的大小是16kb。当然,我们也可以通过参数:innodb_page_size,来重新设置大小。不过,一般情况下,用它的默认值就够了。

好吧,数据页的整体结构已经搞明白了。

那么,单个数据页包含哪些内容呢?

 从上图中可以看出,数据页主要包含如下几个部分:

  • 文件头部
  • 页头部
  • 最大和最小记录
  • 用户记录
  • 空闲空间
  • 页目录
  • 文件尾部

三.用户记录

        对于新申请的数据页,用户记录是空的。当插入数据时,innodb会将一部分空闲空间分配给用户记录。

        用户记录是innodb的重中之重,我们平时保存到数据库中的数据,就存储在它里面。那么,它里面又包含哪些内容呢?你不好奇吗?

其实在innodb支持的数据行格式有四种:

1.compact行格式
2.redundant行格式
3.dynamic行格式
4.compressed行格式
我们以compact行格式为例:

一条用户记录主要包含三部分内容:

        记录额外信息,它包含了变长字段、null值列表和记录头信息。
        隐藏列,它包含了行id、事务id和回滚点。
        真正的数据列,包含真正的用户数据,可以有很多列。
下面让我们一起了解一下这些内容。

3.1 额外信息
        额外信息并非真正的用户数据,它是为了辅助存数据用的。

3.1.1 变长字段列表
        有些数据如果直接存会有问题,比如:如果某个字段是varchar或text类型,它的长度不固定,可以根据存入数据的长度不同,而随之变化。

        如果不在一个地方记录数据真正的长度,innodb很可能不知道要分配多少空间。假如都按某个固定长度分配空间,但实际数据又没占多少空间,岂不是会浪费?

        所以,需要在变长字段中记录某个变长字段占用的字节数,方便按需分配空间。

3.1.2 null值列表
        数据库中有些字段的值允许为null,如果把每个字段的null值,都保存到用户记录中,显然有些浪费存储空间。

        有没有办法只简单的标记一下,不存储实际的null值呢?

答案:将为null的字段保存到null值列表。

        在列表中用二进制的值1,表示该字段允许为null,用0表示不允许为null。它只占用了1位,就能表示某个字符是否为null,确实可以节省很多存储空间。

3.1.3 记录头信息
        记录头信息用于描述一些特殊的属性。

它主要包含:

deleted_flag: 即删除标记,用于标记该记录是否被删除了。
min_rec_flag: 即最小目录标记,它是非叶子节点中的最小目录标记。
n_owned:即拥有的记录数,记录该组索引记录的条数。
heap_no:即堆上的位置,它表示当前记录在堆上的位置。
record_type:即记录类型,其中:0表示普通记录,1表示非叶子节点,2表示Infrimum记录, 3表示Supremum记录。
next_record:即下一条记录的位置。


3.2 隐藏列

数据库在保存一条用户记录时,会自动创建一些隐藏列。如下图所示:

 

 目前innodb自动创建的隐藏列有三种:

  • db_row_id,即行id,它是一条记录的唯一标识。
  • db_trx_id,即事务id,它是事务的唯一标识。
  • db_roll_ptr,即回滚点,它用于事务回滚。

        如果表中有主键,则用主键做行id,无需额外创建。如果表中没有主键,假如有不为null的unique唯一键,则用它做为行id,同样无需额外创建。

        如果表中既没有主键,又没有唯一键,则数据库会自动创建行id。

        也就是说在innodb中,隐藏列中事务id回滚点是一定会被创建的,但行id要根据实际情况决定。

3.3 真正数据列

        真正的数据列中存储了用户的真实数据,它可以包含很多列的数据。

3.4 用户记录是如何相连的?

        通过上面介绍的内容,大家对一条用户记录是如何存储的,应该有了一定的认识。

        但问题来了,一条用户记录和另一条用户记录是如何相连的,innodb是怎么知道,某条记录的下一条记录是谁?

答案是:用前面提到过的, 记录额外信息 》 记录头信息 》下一条记录的位置。

多条用户记录之间通过下一条记录的位置,组成了一个单向链表。这样就能从前往后,找到所有的记录了。 

四.最大和最小记录

        从上面可以得知,在一个数据页当中,如果存在多条用户记录,它们是通过下一条记录的位置相连的。

        不过有个问题:如果才能快速找到最大的记录和最小的记录呢?

        这就需要在保存用户记录的同时,也保存最大和最小记录了。

        最大记录保存到Supremum记录中。

        最小记录保存在Infimum记录中。

        在保存用户记录时,数据库会自动创建两条额外的记录:Supremum 和 Infimum。它们之间的关系,如下图所示:

         从图中可以看出用户数据是从最小记录开始,通过下一条记录的位置,从小到大,一步步查找,最后找到最大记录为止。

五.页目录

        从上面可以看出,如果我们要查询某条记录的话,数据库会从最小记录开始,一条条查找所有记录。如果中途找到了,则直接返回该记录。如果一直找到最大记录,还没有找到想要的记录,则返回空。

咋一看,没有问题。

但如果仔细想想。

效率会不会有点低?

这不是要对整页用户数据进行扫描吗?

有没有更高效的方法?

这就需要使用页目录了。

说白了,就是把一页用户记录分为若干组,每一组的最大记录都保存到一个地方,这个地方就是页目录。每一组的最大记录叫做槽。

由此可见,页目录是有多个槽组成的。所下图所示:

         假设一页的数据分为4组,这样在页目录中,就对应了4个槽,每个槽中都保存了该组数据的最大值。

        这样就能通过二分查找,比较槽中的记录跟需要找到的记录的大小。如果用户需要查找的记录,小于当前槽中的记录,则向上查找上一个槽。如果用户需要查找的记录,大于当前槽中的记录,则向下查找下一个槽。

如此一来,就能通过二分查找,快速的定位需要查找的记录了。

六.文件头部和尾部

6.1 文件头部

        通过前面介绍的行记录中下一条记录的位置和页目录,innodb能非常快速的定位某一条记录。但有个前提条件,就是用户记录必须在同一个数据页当中。

        如果用户记录非常多,在第一个数据页找不到我们想要的数据,需要到另外一页找该怎么办呢?

这时就需要使用文件头部了。

它里面包含了多个信息,但我只列出了其中4个最关键的信息:

页号
上一页页号
下一页页号
页类型
顾名思义,innodb是通过页号、上一页页号和下一页页号来串联不同数据页的。如下图所示:

        不同的数据页之间,通过上一页页号和下一页页号构成了双向链表。这样就能从前向后,一页页查找所有的数据了。

此外,页类型也是一个非常重要的字段,它包含了多种类型,其中比较出名的有:数据页、索引页(目录项页)、溢出页、undo日志页等。

6.2 文件尾部

        我之前提过,数据库的数据是以数据页为单位,加载到内存中,如果数据有更新的话,需要刷新到磁盘上。

        但如果某一天比较倒霉,程序在刷新到磁盘的过程中,出现了异常,比如:进程被kill掉了,或者服务器被重启了。

这时候数据可能只刷新了一部分,如何判断上次刷盘的数据是完整的呢?

这就需要用到文件尾部。

它里面记录了页面的校验和。

        在数据刷新到磁盘之前,会先计算一个页面的校验和。后面如果数据有更新的话,会计算一个新值。文件头部中也会记录这个校验和,由于文件头部在前面,会先被刷新到磁盘上。

        接下来,刷新用户记录到磁盘的时候,假设刷新了一部分,恰好程序出现异常了。这时,文件尾部的校验和,还是一个旧值。数据库会去校验,文件尾部的校验和,不等于文件头部的新值,说明该数据页的数据是不完整的。


七.页头部

通过上面介绍的内容,数据页之间能够轻松访问了,但剩下还有个比较重要的问题,就是记录的状态信息。

比如一页数据到底保存了多条记录,或者页目录到底使用了多个槽等。这些信息是实时统计,还是事先统计好了,保存到某个地方?

为了性能考虑,上面的这些统计数据,当然是先统计好,保存到一个地方。后面需要用到该数据时,再读取出来会更好。这个保存统计数据的地方,就是页头部。

当然页头部不仅仅只保存:槽的数量、记录条数等信息。

它还记录了:

        已删除记录所占的字节数
        最后插入记录的位置
        最大事务id
        索引id
        索引层级

八.总结

        多个数据页之间通过页号构成了双向链表。而每一个数据页的行数据之间,又通过下一条记录的位置构成了单项链表。整体架构图如下:

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mysql底层数据存储原理 的相关文章

  • 如何为 MySQL 和 Postgres 编写不区分大小写的查询?

    我在本地运行 MySQL 数据库进行开发 但部署到使用 Postgres 的 Heroku Heroku 处理几乎所有事情 但我不区分大小写的 Like 语句变得区分大小写 我可以使用 iLike 语句 但我的本地 MySQL 数据库无法处
  • 在 ASP.NET MVC 中使用 MySQL 的 AccountController

    在 Visual Studio 中创建默认的 ASP NET MVC 项目会设置一个可以在其中注册用户的基本项目 我将如何继续更改它以使用 MySQL 服务器而不是 SQLServer 现在可以使用了 安装最新的 Connector NET
  • 导入 CSV 以更新表中的行

    大约有 26K 个产品 帖子 每个产品都有如下元值 post id 列是数据库中的产品 ID sku meta key 是每个产品的唯一 ID 我收到了一个新的 CSV 文件 该文件更新了每个产品的 sale price meta key
  • Mysql为简单频繁查询创建排序索引性能

    我正在处理一个包含大约 400 万条消息条目的 mysql 表 并尝试根据时间戳选择最新的 50 条消息 另一个要求是返回的消息不以固定前缀开头 问题是单个查询大约占用 25 的 cpu 并且最多需要 1 5 秒 该查询经常由多个客户端执行
  • 使用另一个表中的数据查找并替换 MySQL 中的字符串

    我有两个 MySQL 表 我想使用另一个表中的数据查找和替换一个表中的文本字符串 Table texts messages thx guys i think u r great thx again u rock Table dictiona
  • 如何在 Windows 上安装 PHP 的 PDO 驱动程序?

    我在 Windows 服务器上安装了 Apache PHP 5 6 和 MySQL 5 7 在 php ini 中 我启用了以下内容 extension php mysql dll extension php mysqli dll exte
  • mySQL 返回可能有重复项的随机行

    我正在尝试随机化一定数量的行 但假设数据库中只有 4 行 而我需要获得 6 个随机行 我希望有可能 即使表中有超过 6 行 产生重复的行行 这在 mySQL 中很容易实现吗 我当前的查询是这样的 SELECT FROM winners OR
  • MySQL 错误 1290 (HY000) --secure-file-priv 选项

    我试图在我的脚本中使用以下代码将 MySQL 脚本的结果写入文本文件 SELECT p title p content c name FROM post p LEFT JOIN category c ON p category id c i
  • MySQL:你能指定一个随机限制吗?

    有没有办法在 SQL MySQL 中随机化限制数字 我希望能够做的是在查询中获取随机数量的结果以在插入子查询中使用 而无需任何服务器端脚本 我希望能够作为假设说明运行的查询是 SELECT id FROM users ORDER BY RA
  • Rails 多租户架构,限制多个租户的访问范围

    目前我们有一个单租户数据库架构 MySQL 运行着超过 100 个数据库 我们使用 Apartment gem 切换子域上的数据库连接 一切都很顺利 然而 我们现在需要创建所谓的 伞 客户端 它可以访问一组现有客户端的所有数据 我不认为这对
  • 需要有关使用 PHP 在 mysql 数据库中插入逗号分隔数据的帮助

    数据库表中已有的演示数据 INSERT INTO csvtbl ID SKU Product Name Model Make Year From Year To VALUES 1 C2AZ 3B584 AR Power Steering P
  • PHP 中的异步数据库/服务调用:Gearman 与 pthreads

    在我们的 LAMP 站点上 我们遇到一些服务必须多次调用数据库才能提取数据的问题 通常在 PHP 中完成此操作的方式 至少我的经验 是串行的 这显然是低效的 我们可以通过使用缓存和聚合一些查询来缓解一些低效率的问题 但在某些情况下我们仍然需
  • 使用 mysql2 gem 获取最后插入的 id

    我有这样的代码 require mysql2 db query insert into clients Name values client 我可以通过 1 个查询返回最后插入的 ID 吗 您可以使用last id客户端实例的方法 clie
  • 显示过去 7 天 PHP 的结果

    我想做的是显示过去 30 天的文章 但我现有的代码不断给我一个 mysql fetch assoc 错误 然后追溯到我的查询 这是代码 sql mysql query SELECT FROM table WHERE DATE datetim
  • MySQL 5左连接未知列

    我有以下查询在 mysql 4 1 中工作 但在 5 0 中不起作用 SELECT FROM email e event email ee LEFT JOIN member m on m email e email WHERE ee ema
  • 从数据库生成 XML 时出现 PHP 编码错误 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我正在尝试获取一个简单的 PHP 服
  • 物理写入文件已满 - mysql 错误

    我正在使用xampp 每次启动mysql时 我都会在xampp中收到以下错误 Error MySQL shutdown unexpectedly 13 16 14 mysql This may be due to a blocked por
  • 基本表创建 fpdf

    我找不到使用 fpdf 制作表格并从 mysql 数据库获取数据的合适教程 我只是想知道如何创建一个 我在网上尝试示例时遇到了很多错误 例如 我有 名字 中间名 姓氏 年龄 和 电子邮件 列 如何使用 fpdf 创建表格并回显数据库中的条目
  • Laravel Sum 列数据库 Eloquent

    尝试获取我的一个表中 int 字段的总和应该非常容易 不幸的是 事实并非如此 因为无论我使用 Laravel MySQL 还是 Excel 我都会得到不同的结果 Laravel 5 4 给了我20506 Table sum field na
  • Laravel Group By 和 Order By 不起作用

    我尝试制作一个Laravel 5 8项目 项目中的数据是这样的 id purch name prcvalue 1 10234 Nabila 100 2 10234 Nadeera 450 3 10234 Nabila 540 4 10234

随机推荐

  • Delphi程序使用资源的释放

    写出一个不会造成资源分配崩溃的Delphi应用程序的 关键是确保如果程序中分配了资源 即使当故障发生 程 序也应能释放占用的资源 文件 内存 WINDOWS资源和对象是一些必须时时加 以注意的需确保释放的资源 下面的事件控制代码例子中 先分
  • 详细SysTick定时器(+对寄存器段位的解释)

    1 首先 systick定时器 嘀嗒定时器 隶属于CM4内核的一个外设 内嵌在NVIC中 所以一些函数在core cm4 c中 系统定时器是一个24bit向下递减的计数器 计数器每次计数时间为1 SYSCLK SYSCLK gt 系统时钟
  • 文库系统PPT docx pdf文档转换系统在线预览知识付费程序源码 二次开发

    基于PHP MYSQL开发的适用于多平台的文档管理系统 支持doc ppt excel pdf 压缩包 图片 音视频 等资源的在线预览和下载 响应速度更快速对SEO更友好 收录更快 排名更强 内置文章 问题 广告管理 TAG标签 内部链接等
  • Grafana

    grafana 是一款采用 go 语言编写的开源应用 主要用于大规模指标数据的可视化展现 是网络架构和应用分析中最流行的时序数据展示工具 目前已经支持绝大部分常用的时序数据库 最好的参考资料就是官网 http docs grafana or
  • typora数学符号大全

    转载于 https www cnblogs com mmmqqdd p 10803576 html
  • Gson,FastJson,JackJson,ProtoBuf 数据格式转换详解

    1 Gson 依赖 implementation com google code gson gson 2 8 6 Gson gson new Gson 实体类 Bean 转JSON String json gson toJson new B
  • 解决IDEA使用Maven模板创建项目时初始化过慢一直卡着不动问题

    选择模板后为Maven添加参数archetypeCatalog 值为internal
  • 实战级详解Spring框架中引入阿里开源组件Nacos作配置中心

    Nacos的配置中心 先来聊聊配置中心是什么 以及为什么要使用配置中心 简单的说 配置中心是可以集中 灵活 动态的管理系统中的各种配置参数的配置管理产品 例如增 删 改 查功能开关 数据库连接配置 服务请求地址等 所谓集中 就是有统一管理的
  • STM32F103ZET6---【硬件篇】DMA简介

    STM32 最多有 2 个 DMA 控制器 DMA2 仅存在大容量产品中 DMA1 有 7 个通道 DMA2 有 5个通道 每个通道专门用来管理来自于一个或多个外设对存储器访问的请求 还有一个仲裁来协调各个 DMA 请求的优先权 DMA主要
  • [2023]自动化测试框架完整指南

    所有软件在提供给用户之前都必须经过测试 软件测试是开发生命周期中必不可少的一步因为它确保用户必须收到符合其开发目的的高质量产品 每个企业都优先考虑测试 因此 大多数人更愿意从手动测试转向自动化 因此 自动化测试框架是任何软件测试过程的基础
  • html登录 页面连接数据库,连接数据库实现登录页面

    1 数据库Database booksshop 表的结构 think user CREATE TABLE IF NOT EXISTS think user user id int 11 NOT NULL user name varchar
  • JSON字符串直接转换为目标对象,并测试是否是深度转换

    文章目录 1 依赖 2 实体类 3 测试用例 4 测试 5 结果 提前说结果 可以深度转换对象和数组JSON字符串 1 依赖
  • 特征工程(一)countvectororizer

    将原始数据的word特征数字化为countvector特征 并将结果保存到本地 article特征可做类似处理 import pandas as pd from sklearn feature extraction text import
  • 本地电脑搭建SFTP服务器,并实现公网访问

    本地电脑搭建SFTP服务器 并实现公网访问 1 搭建SFTP服务器 1 1 下载 freesshd 服务器软件 下载地址 freeSSHd and freeFTPd 选择freeFTPD exe下载 下载后 点击安装 安装之后 它会提示是否
  • 虚拟化技术-KVM的安装及管理

    1 宿主机环境准备 KVM需要宿主机CPU必须支持虚拟化功能 因此如果是在vmware workstation上使用虚拟机做宿主机 那么必须要在虚拟机配置界面的处理器选项中开启虚拟机化功能 1 CPU开启虚拟化 硬件 处理器 2核 勾选虚拟
  • Dsadd批量域帐号(详细属性)添加方法

    由于网上的都不够详细 鼓搞了很久 理清了 贴上来大家需要的用下 先了解一下 dsadd 命令 该命令只有2003服务器系统以上才存在 系统开始运行输入Cmd dsadd dsadd computer 将计算机添加到目录 dsadd cont
  • mysql数据库非正常关机报错,启动时mysql被killed掉

    阿里云服务器centos7 6 mysql数据库5 6 44 mysql启动报错 usr bin mysqld safe line 183 15006 Killed nohup usr sbin mysqld basedir usr dat
  • 不翻墙安装油猴

    不翻墙安装油猴 https chrome zzzmh cn info token dhdgffkkebhmkfjojejmpbldmpobfkfo 利用极简插件安装
  • 解决adb connect 连接Android设备报错:由于目标计算机积极拒绝,无法连接

    解决adb connect 连接Android设备报错 由于目标计算机积极拒绝 无法连接 神马东东 目录 一 问题描述 二 解决方法 需Root 三 解决方法 不需要Root 一 问题描述 在使用adb连接Android设备时 可以通过有线
  • mysql底层数据存储原理

    一 前言 1 1 数据存储在哪里 操作系统从寄存器中读取数据是最快的 因为它离CPU最近 但是寄存器有个非常致命的问题是 它只能存储非常少量的数据 设计它的目的主要是用来暂存指令和地址 并非存储大量用户数据的 内存的大小虽然比寄存器大 但是