MySQL InnoDB数据存储结构

本篇涉及到的内容

1. 数据库的存储结构:页

索引结构给我们提供了高效的索引方式,不过索引信息以及数据记录都是保存在文件上的,确切说是存储在页结构中。另一方面,索引是在存储引擎中实现的,MySQL服务器上的存储引擎负责对表中数据的读取和写入工作。

不同的存储引擎中存放的格式一般是不同的,甚至有的存储引擎比如:Memory都不用磁盘来存储数据。

由于InnoDB是MySQL的默认存储引擎,索引本章主要介绍InnoDB存储引擎的数据存储结构。

1.1 磁盘与内存交互基本单位:页

1.2 页结构概述

页a、页b、页c . . .页n 这些页可以不在物流结构上相连,只要通过双向链表相关联即可。每个数据页中的记录会按照主键值从小到大的顺序组成一个单向链表,每个数据页都会为存储在它里面的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。

1.3 页的大小

不同的数据库管理系统(简称DBMS)的页大小不同。比如在 MySQL 的 InnoDB 存储引擎中,默认页的大小是 16KB,我们可以通过下面的命令来进行查看:

show variables like '%innodb_page_size%'

SQL Server 中页的大小为 8KB,而在 Oracle 中我们用术语 “” (Block)来表示 “页”,Oracle 支持的快大小为2KB, 4KB, 8KB, 16KB, 32KB 和 64KB。

1.4 页的上层结构

另外在数据库中,还存在着区(Extent)、段(Segment)和表空间(Tablespace)的概念。行、页、区、段、表空间的关系如下图所示:

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

  • 段(Segment):由一个或多个 区 组成,区在文件系统是一个连续分配的空间(在InnoDB中是连续的 64 个页),不过在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段。

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

2. 页的内部结构

页如果按类型划分的话,常见的有 数据页(保存B+树节点)、系统表、Undo 页 和 事务数据页 等。数据页是我们最常使用的页。

数据页的 16KB 大小的存储空间被划分为七个部分,分别是文件头(File Header)、页头(Page Header)、最大最小记录(Infimum + supremum)、用户记录(User Records)、空闲空间(Free Space)、页目录(Page Directory)和文件尾(File Tailer)。

页结构的示意图如下所示:

如下表所示:

我们可以把这7个结构分为3个部分。

第一部分:File Header (文件头部) 和 File Trailer (文件尾部)

见文件InnoDB数据库存储结构.mmap

第二部分:User Records (用户记录)、最大最小记录、Free Space (空闲空间)

见文件InnoDB数据库存储结构.mmap

第三部分:Page Directory (页目录) 和 Page Header (页面头部)

见文件InnoDB数据库存储结构.mmap

2.3 从数据库页的角度看B+树如何查询

一颗B+树按照字节类型可以分为两部分:

  1. 叶子节点,B+ 树最底层的节点,节点的高度为0,存储行记录。
  2. 非叶子节点,节点的高度大于0,存储索引键和页面指针,并不存储行记录本身。

当我们从页结构来理解 B+ 树的结构的时候,可以帮我们理解一些通过索引进行检索的原理:

3. InnoDB行格式 (或记录格式)

见文件InnoDB数据库存储结构.mmap

4. 区、段与碎片区

4.1 为什么要有区?

B+树的每一层中的页都会形成一个双向链表,如果是以页为单位来分配存储空间的话,双向链表相邻的两个页之间的物流位置可能离得非常远。介绍B+树索引的适用场景的时候特别提到范围查询只需要定位到最左边的记录和最右边的记录,然后沿着双向链表一直扫描就可以了,而如果链表中相邻的两个页物理位置离得非常远,就是所谓的随机I/O。再一次强调,磁盘的速度和内存的速度差了好几个数量级,随机I/O是非常慢的,所以我们应该尽量让链表中相邻的页的物流位置也相邻,这样进行范围查询的时候才可以使用所谓的顺序 I/O。

引入区的概念,一个区就是在物理位置上连续的64个页。因为 InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64 * 16KB = 1MB。在表中数据最大的时候,为某个索引分配空间的时候就不再按照页为分单位分配了,而是按照区位单位分配,甚至在表中的数据特别多的时候,可以一次性分配多个连续的区。虽然可能造成一点点空间的浪费(数据不足以填充满整个区),但是从性能角度看,可以消除很多的随机I/O,功大于过!!!

4.2 为什么要有段?

对于范围查询,其实是对B+树叶子节点中的记录进行顺序扫描,而如果不区分叶子节点和非叶子节点,统统把节点代表的页面放到申请到的区中的话,进行范围扫描的效果就大打折扣了。所以InnoDB对B+树的叶子节点 和 非叶子节点 进行了区别对待,也就是说叶子节点有自己独有的区,非叶子节点也有自己独有的额区。存放叶子节点的区的集合就算是一个 段(Segment),存放非叶子节点的区的集合也算是一个段。也就是说一个索引会生成2个段,一个叶子节点段,一个非叶子节点段。

除了索引的叶子节点段和非叶子节点段之外,InnoDB中还有为存储一些特殊的数据而定义的段,比如回滚段,所以,常见的段有数据段、索引段、回滚段。数据段即为B+树的叶子节点,索引段即为B+树的非叶子节点

在InnoDB存储引擎中,对段的管理都是由引擎自身所完成,DBA不能也没有必要对其进行控制。这从一定程度上简化了DBA对于段的管理。

段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。

4.3 为什么要有碎片区?

默认情况下,一个使用InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段,而段是以区为单位申请存储空间的,一个区默认占用1M(64 * 16KB = 1024KB)存储空间,所以默认情况下一个只存了几条记录的小表也需要2M的存储空间,以后每次添加一个索引都要多申请2M的存储空间。这对于存储记录比较少的表太过于浪费。这个问题的症结在于到现在为止介绍的区都是非常纯粹的,也就是一个区被整个分配给某个段,或者说区中的所有页都是为了存储同一个段的数据而存在的,即使段的数据填不满区中所有的页面,那剩余的页也不能挪作他用。

为了考虑以完整的区为单位分配给某个段对于数据量较小的表太浪费存储空间的这种情况,InnoDB提出了一个碎片(Fragment)区的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间,并不属于任何一个段。

所以以后为某个段分配存储空间的策略如下:

  • 在刚开始想表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的。

  • 当某个段已经占用了32个碎片区页面之后,就会申请以完整的区为单位来分配存储空间。

现在段不能仅定义为是某些区的集合,更精确的应该是 某些零散的页面以及一些完整的区的集合。

4.4 区的分类

区大体上可以分为4种类型:

  • 空闲的区 (FREE) : 现在还没有用到这个区中的任何页面。
  • 有剩余空间的碎片区 (FREE_FRAG):表示碎片区中还有可用的页面。
  • 没有剩余空间的碎片区 (FULL_FRAG):表示碎片区中的所有页面都被使用,没有空闲页面。
  • 附属于某个段的区 (FSEG):每一个索引都可以分为叶子节点段和非叶子节点段。

处于FREE、FREE_FRAG 以及 FULL_FRAG 这三种状态的区都是独立的,直属于表空间。而处于 FSEG 状态的区是附属于某个段的。

如果把表空间比作是一个集团军,段就相当于师,区就相当于团。一般的团都是隶属于某个师的,就像是处于 FSEG 的区全部隶属于某个段,而处于 FREE、FREE_FRAG 以及 FULL_FRAG 这三种状态的区却直接隶属于表空间,就像独立团直接听命于军部一样。

5. 表空间

表空间可以看做是InnoDB存储引擎逻辑架构的最高层,所有的数据都存放在表空间中。表空间是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。表空间数据库由一个或多个表空间组成,表空间从管理上可以划分为系统表空间(System Tablespace)、独立表空间(File-Per-Table Tablespace)、撤销表空间(Undo Tablespace)和临时表空间(Temporary Tablespace)

5.1 独立表空间

独立表空间,即每张表有一个独立的表空间,也就是数据和索引信息都会保存在自己的表空间中。独立的表空间 (即:单表) 可以在不同的数据库之间进行 迁移

空间可以回收 (DROP TABLE 操作可自动回收表空间;其他情况,表空间不能自己回收) 。如果对于统计分析或是日志表,删除大量数据后可以通过:alter table TableName engine=innodb; 回收不用的空间。对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理。

独立表空间结构

独立表空间由段、区、页组成。

真实表空间对应的文件大小

我们到数据目录里看,会发现一个新建的表对应的 .ibd 文件只占用了 96K,才6个页面大小 (MySQL5.7中),这是因为一开始表空间占用的空间很小,因为表里边都没有数据。不过别忘了这些 .ibd 文件是自扩展的,随着表中数据的增多,表空间对应的文件也逐渐增大。

查看 InnoDB 的表空间类型:

show variables like 'innodb_file_per_table'

你能看到 innodb_file_per_table=ON, 这就意味着每张表都会单词保存一个 .ibd 文件。

5.2 系统表空间

系统表空间的结构和独立表空间基本类似,只不过由于整个MySQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,这部分是独立表空间中没有的。

InnoDB数据字典

每当我们向一个表中插入一条记录的时候,MySQL校验过程如下:先要校验一下插入语句对应的表存不存在,插入的列和表中的列是否符合,如果语法没有问题的话,还需要直到该表的聚簇索引和所有二级索引对应的根页面是哪个页面,然后把记录插入对应索引的B树中。所以说,MySQL除了保存着我们插入的用户数据之外,还需要保存许多额外的信息,比方说:

  • 某个表属于哪个表空间,表里边有多少列。

  • 表对应的每一个列的类型是什么。

  • 该表有多少索引,每个索引对应哪几个字段,该索引对应的根页在哪个表空间的哪个页。

  • 该表有哪些外键,外键对应哪个表的哪些列。

  • 某个表空间对应文件系统上文件路径是什么。

删除这些数据并不是我们使用 INSERT 语句插入的用户数据,实际上是为了更好的管理我们这些用户数据而不得以引入的一些额外数据,这些数据页称为 元数据。InnoDB 存储引擎特意定义了一些列的 内部系统表 (internal system table) 来记录这些元数据:

这些系统表也称为 数据字典,它们都是以 B+ 树的形式保存在系统表空间的某个页面中。其中 SYS_TABLES、SYS_COLUMNS、SYS_INDEXES、SYS_FIELDS 这四个表尤其重要,称之为基本系统表 (basic system tables) ,我们先看看这4个表的结构:

注意:用户不能直接访问 InnoDB 的这些内部系统表,除非你直接去解析系统表空间对应文件系统上的文件。不过考虑到查看这些表的内容可能有助于大家分析问题,所以在系统数据库 information_schema 中提供了一些以 innodb_sys 开头的表:

USE information_schema;
SHOW TABLES LIKE 'innodb_sys%';

information_scheme 数据库中的这些以 INNODB_SYS 开头的表并不是真正的内部系统表 (内部系统表就是我们上边以 SYS 开头的那些表),而是在存储引擎启动时读取这些以 SYS 开头的系统表,然后填充到这些以 INNODB_SYS 开头的表中。以 INNODB_SYS 开头的表和以 SYS 开头的表中的字段并不完全一样,但仅供大家参考已经足矣。

附录:数据页加载的三种方式

InnoDB从磁盘中读取数据 最小单位 是数据页。而你想得到的 id = xxx 的数据,就是这个数据页众多行中的一行。

对于MySQL存放的数据,逻辑概念上我们称之为表,在磁盘等物理层面而言是按 数据页 形式进行存放的,当其加载到 MySQL 中我们称之为 缓存页

如果缓冲池没有该页数据,那么缓冲池有以下三种读取数据的方式,每种方式的读取速率是不同的:

1. 内存读取

如果该数据存在于内存中,基本上执行时间在 1ms 左右,效率还是很高的。

2. 随机读取

如果数据没有在内存中,就需要在磁盘上对该页进行查询,整体时间语句在 10ms 左右,这 10ms 中有 6ms是磁盘的实际繁忙时间(包括了寻道和半圆旋转时间),有 3ms 是对可能发生的排队事件的估计,另外还有 1ms 的传输时间,将页从磁盘服务器缓冲区传输到数据库缓冲区中。这 10ms 看起来很快,但实际上对于数据库来说消耗的时间已经非常长了,因为这还是一个页的读取时间。

3. 顺序读取

顺序读取其实是一种批量读取的方式,因为我们请求的数据在磁盘上往往都是相邻存储的,顺序读取可以帮助我们批量读取页面,这样的话,一次性加载到缓冲池中就不需要再对其他页单独进行磁盘I/O操作了。如果一个磁盘的吞吐量是 40MB/S,那么对于一个 16KB大小的页来说,一次可以顺序读取 2560(40MB/16KB)个页,相当于一个页的读取时间为 0.4ms。采用批量读取的方式,即使是从磁盘上进行读取,效率也比从内存中只单独读取一个页的效率要高。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【漏洞复现】Fastjson_1.2.47_rce

感谢互联网提供分享知识与智慧,在法治的社会里,请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞检测3、漏洞验证 1.5、深度利用1、反弹Shell 说明内容漏洞编号漏洞名称Fastjson_1.2.47_远程执行漏…

【HarmonyOS】服务卡片 API6 JSUI跳转不同页面并携带参数

【关键字】 服务卡片、卡片跳转不同页面、卡片跳转页面携带参数 【写在前面】 本篇文章主要介绍开发服务卡片时,如何实现卡片点击跳转不同页面,并携带动态参数到js页面。在此篇文章“服务卡片 API6 JSUI跳转不同页面”中说明了如果跳转不同页面&#xf…

Py之auto-gptq:auto-gptq的简介、安装、使用方法之详细攻略

Py之auto-gptq:auto-gptq的简介、安装、使用方法之详细攻略 目录 auto-gptq的简介 1、版本更新历史 2、性能对比 推理速度 困惑度(PPL) 3、支持的模型 3、支持的评估任务 auto-gptq的安装 auto-gptq的使用方法 1、基础用法 (1)、量…

2023-11-04:用go语言,如果n = 1,打印 1*** 如果n = 2,打印 1*** 3*** 2*** 如果n = 3,打印

2023-11-04:用go语言,如果n 1,打印 1*** 如果n 2,打印 1***3*** 2*** 如果n 3,打印 1***3*** 2***4*** 5*** 6*** 如果n 4,打印 1***3*** 2***4*** 5*** 6***10** 9*** 8*** 7*** 输入…

立创eda专业版学习笔记(8)(运行模式)

以前没注意过这个问题,我有2台电脑,都能登录eda专业版,但是一台是全在线模式,另外一台是半离线模式,虽然是同一个账号,但是打开里面的工程会发现,两边的工程完全不同,因为一台的工程…

shell script 的默认变量$0,$1,$2...,参数偏移的shift

简单来说,在scirpt脚本里面,$0表示文件名,$1表示第一个参数,以此类推,还有 $# 后面接参数的个数 $ 代表"$1","$2","$3",每个都是独立的,用双引号括起来 $* 代…

APP攻防--ADB基础

进入app包 先使用 adb devices查看链接状态 手机连接成功的 adb shell 获取到手机的一个shell 此时想进入app包时没有权限的,APP包一般在data/data/下。没有执行权限,如图 Permission denied 权限被拒绝 此时需要手机root,root后输入 su …

Dual Personalization on Federated Recommendation

联合推荐的双重个性化 代码链接 论文链接 主要创新 文章的主要创新是提出了一种双重个性化机制,用于增强联邦推荐系统的性能。这种机制能够在联邦学习框架中为每个本地模型学习用户特定的物品嵌入,从而显著提升推荐系统的效果。通过在四个数据集上进行…

FreeRTOS-定时器\二值信号互斥信号\事件组

FreeRTOS整体知识框架可以参考下文: FreeRTOS整体知识框架 一. FreeRTOS定时器 FreeRTOS定时器 博文路径 FreeRTOS提供了一种软件定时器,用来快速实现一些周期性的操作,并且节约了硬件定时器。不过尽量还是不要做过多的操作,以免影响其他…

office2024下载详细安装教程

简单的说 Office 是一款由Microsoft 开发的一套办公软件,里面包含了常用的办公组件而其中就包含了Word、Excel、PowerPoint、Access等! 并且office是目前最常用的一类办公软件,使用它可以解决日常生活和工作中遇到的很多问题。 熟练掌握offi…

【信息安全原理】——IP及路由安全(学习笔记)

目录 🕒 1. IPv4协议及其安全性分析🕒 2. IPsec(IP Security)🕘 2.1 IPsec安全策略🕤 2.1.1 安全关联(Security Association, SA)🕤 2.1.2 安全策略(Security…

【lvgl】linux开发板搭建环境

前言 本章介绍如何在linux开发板准备好了fb0的情况下移植lvgl。 抓取源码 git clone https://github.com/lvgl/lvgl.git git clone https://github.com/lvgl/lv_drivers.git git clone https://github.com/lvgl/lv_demos.git git clone https://github.com/lvgl/lv_port_lin…

【教3妹学编程-算法题】使数组变美的最小增量运算数

2哥 : 3妹,脸上的豆豆好了没呢。 3妹:好啦,现在已经没啦 2哥 : 跟你说很快就会消下去的,还不信~ 既然你的容颜和心情都如此美丽,那我们就再做一道关于美丽的题吧。 3妹:切,2哥就会取笑我&#x…

搬家两年随笔

不知不觉中,我已经搬到这个地方两年多了。 回首这段时间,我感触颇深。 尽管这里地理位置较为偏僻,交通不是特别方便,但环境优美,绿树成荫,空气清新。 只是相对于之前的生活环境,这里离上班的地方…

【入门Flink】- 02Flink经典案例-WordCount

WordCount 需求&#xff1a;统计一段文字中&#xff0c;每个单词出现的频次 添加依赖 <properties><flink.version>1.17.0</flink.version></properties><dependencies><dependency><groupId>org.apache.flink</groupId><…

根据Word模板,使用POI生成文档

突然想起来有个小作业&#xff1a;需要根据提供的Word模板填充数据。这里使用POI写了一个小demo验证下。 测试用模板&#xff1a; 执行结果 1.引入依赖坐标 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId&…

Emscripten + CMakeLists.txt 将 C++ 项目编译成 WebAssembly(.wasm)/js,并编译 Html 测试

背景&#xff1a;Web 端需要使用已有的 C 库&#xff08;使用 CMake 编译&#xff09;&#xff0c;需要将 C 项目编译成 WebAssembly(.wasm) 供 js 调用。 上篇文章《Mac 上安装 Emscripten》 已讲解如何安装配置 Emscripten 环境。 本篇文章主要讲解如何将基于 CMakeLists 配…

CSS解决div行变块 ➕ CSS解决“table中的td文字溢出控制显示字数,显示省略号”的问题

CSS解决div行变块 ➕ CSS解决“table中的td文字溢出控制显示字数&#xff0c;显示省略号”的问题 1. div变块级设置1.1 先看不设置的效果1.2 再看设置之后的效果 2. 解决 table 中 td 内容过长问题2.1 CSS实现&#xff08;文字溢出控制td显示字数&#xff0c;显示省略号&#x…

Bytedance揭秘OpenAI大模型: GPT-3到GPT-4进化路径

文章目录 探秘GPT-3到GPT-4进化之路1、SFT&#xff1a;早期GPT进化的推动者2、RLHF和SFT&#xff1a;编码能力提升的功臣3、代码加入预训练&#xff0c;对推理帮助最大4、“跷跷板”现象 论文地址项目链接Reference GPT-Fathom: Benchmarking Large Language Models to Deciphe…

基于Qt Designer 操作教程

​本章将简介使用 Qt Creator 里自带的 Qt Designer,使用 Qt Designer 比较方便的构造 UI 界面。特点是方便布局,比较形象。 ## 使用 UI 设计器开发程序 在这小节里我们继续学习如何使用 Qt Designer 开发程序,Qt Designer 是属于 Qt Creator 的一个功能而已,大家不要搞混…
最新文章