【MySQL索引与优化篇】InnoDB数据存储结构
文章目录
- 1. 数据库的存储结构:页
- 1.1 磁盘与内存交互基本单位:页
- 1.2 页结构概述
- 1.3 页的上层结构
- 2. 页的内部结构
- 3. InnoDB行格式(或记录格式)
- 3.1 Compact行格式
- 3.2 Dynamic和Compressed行格式
- 3.3 Redundant行格式
- 4. 区、段与碎片区
- 4.1 为什么要有区?
- 4.2 为什么要有段?
- 4.3 为什么有碎片区
- 4.4 区的分类
- 5. 表空间
- 5.1 独立表空间
- 5.2 系统表空间
1. 数据库的存储结构:页
索引结构给我们提供了高效的索引方式,不过索引信息以及数据记录都是保存在文件上的,确切说是存储在页结构中。另一方面,索引是在存储引擎中实现的,MySQL服务器上的 存储引擎
负责对表中数据的读取和写入工作。不同存储引擎中 存放的格式
一般是不同的。
由于 InnoDB
是MySOL的 默认存储引擎
,所以本章剖析InnoDB存储引擎的数据存储结构。
1.1 磁盘与内存交互基本单位:页
InnoDB 将数据划分为若干个页,InnoDB中页的大小默认为 16KB(可通过show variables命令看innodb_page_size参数)。页
是磁盘与内存交互的 基本单位
。
记录是按照行来存储的,但是数据库的读取并不以行为单位,否则一次读取(也就是一次 /0 操作)只能处理一行数据,效率会非常低
1.2 页结构概述
页a、页b、页c…页n 这些页可以 不在物理结构上相连
,只要通过 双向链表
相关联即可。每个数据页中的记录会按照主键值从小到大的顺序组成一个 单向链表
,每个数据页都会为存储在它里边的记录生成一个 页目录
,在通过主键查找某条记录的时候可以在页目录中 使用二分法
快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
1.3 页的上层结构
另外在数据库中,还存在着区 (Extent)、段 (Segment) 和表空间 (Tablespace) 的概念。行、页、区、段、表空间的关系如下图所示:
区(Extent)是比页大一级的存储结构,在InnoDB 存储引擎中,一个区会分配 64 个连续的页。因为InnoDB 中的页大小默认是 16KB,所以一个区的大小是 64*16KB= 1MB。
段(Segment)由一个或多个区组成,在段中不要求区与区之间是相邻的。段是数据库中的分配单位,不同类型的数据库对象以不同的段形式存在。当我们创建数据表、索引的时候,就会相应创建对应的段,比如创建一张表时会创建一个表段,创建一个索引时会创建一个索引段
表空间(Tablespace)是一个逻辑容器,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。数据库由一个或多个表空间组成,表空间从管理上可以划分为 系统表空间
、 用户表空间
、撤销表空间
、临时表空间
等。
2. 页的内部结构
页如果按类型划分的话,常见的有 数据页(保存 B+ 节点)
、系统页
、Undo 页
和 事务数据页
等。数据页是我们最常使用的页。
数据页的 16KB 大小的存储空间被划分为七个部分,分别是文件头 (File Header) 、页头(Page Header) 、最大最小记录 (Infimum+supremum) 、用户记录 (User Records)、空闲空间 (Free Space) 、页目录 (Page Directory) 和文件尾 (File Tailer) 。
这7个部分的作用说明分别如下:
B+ 树是如何进行记录检索的?
通过 B+ 树的索引查询行记录,首先是从 B+ 树的根开始,逐层检索,直到找到叶子节点,也就是找到对应的数据页为止,将数据页加载到内存中,页目录中的槽(sot)采用 二分查找
的方式先找到一个粗略的记录分组(一个分组有8条以下的记录,页目录结构查找图如下),然后再在分组中通过 链表遍历
的方式查找记录。
3. InnoDB行格式(或记录格式)
我们平时的数据以行为单位来向表中插入数据,这些记录在磁盘上的存放方式也被称为行格式
或者记录格式
。InnoDB存储引擎设计了4种不同类型的行格式,分别是Compact
、Redundant
、Dynamic
和 Compressed
行格式。
查看MySQL8.0默认的行格式或具体表使用的行格式:
SELECT @@innodb_default_row_format; -- 默认dynamicSHOW TABLE STATUS LIKE '表名'; -- 查看具体表使用的行格式Row_format列
3.1 Compact行格式
-
变长字段长度列表:存储所有变长字段占用字节数,存储顺序是反的
-
NULL值列表:1代表NULL,0代表非NULL,如果表中没有允许存储NULL的值,则NULL值列表也不存在了,故可考虑尽可能设置表字段为非NULL,注意:存储的顺序也是反的,下面有示意图
-
记录头信息(5字节)
-
预留位1和2:各占1bit,没有使用
-
delete_mask:占1bit,删除标识
-
min_rec_mask:占1bit,B+树的每层非叶子节点中的最小记录都会添加该标记
-
n_owned:占4bit,表示当前记录槽拥有的记录数,只有组中最大的会记录
-
heap_no:占13bit,表示当前记录在记录堆的位置信息
-
record_type:占3bit,表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录
-
next_record:占16bit,表示下一条记录的相对位置
-
-
记录的真实数据:除了自己定义的列的数据外,还会有三个隐藏列
- DB_ROW_ID:6字节,行ID,唯一标识一条记录,一个表即没有主键又没有唯一索引则会默认添加该项隐藏列作为主键
- DB_TRX_ID:事务ID
- DB_ROLL_PTR:回滚指针
NULL值列表示意图:
记录头信息示意图:
3.2 Dynamic和Compressed行格式
Dynamic和Compressed相比Compact行格式只是对于行溢出的处理方式不同。
行溢出:一个页的大小一般是16KB,也就是16384字节,而一个VARCHAR(M)类型的列就最多可以存储65533个字节(2个字节存储变长的长度,1个存储NULL标识),这样就可能出现一个页存放不了条记录,这种现象称为行溢出
- Compressed和Dynamic两种记录格式对于存放在BLOB中的数据采用了完全的行溢出的方式。如图,在数据页中只存放20个字节的指针(溢出页的地址),实际的数据都存放在Off Page(溢出页) 中
- Compressed行记录格式的另一个功能就是,存储在其中的行数据会以zlib的算法进行压缩,因此对于BLOB、TEXT、VARCHAR这类大长度类型的数据能够进行非常有效的存储
- Compact和Redundant两种格式会在记录的真实数据处存储一部分数据(存放768个前缀字节),把剩余的数据分散存储在几个其他的页中进行分页存储然后记录的真实数据处用20个字节存储指向这些页的地址(当然这20个字节中还包括这些分散在其他页面中的数据的占用的字节数),从而可以找到剩余数据所在的页
Compact和Redundant页的扩展:
Compressed和Dynamic:
3.3 Redundant行格式
支持该格式是为了兼容MySQL 5.0之前版本的页格式,实际开发中不使用,故不作过多分析
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个段,一个 叶子节点段
,一个 非叶子节点段
。
除了索引的叶子节点段和非叶子节点段之外,lnnoDB中还有为存储一些特殊的数据而定义的段,比如回滚段。所以,常见的段有 数据段
、索引段
、 回滚段
。数据段即为B+树的叶子节点,索引段即为B+树的非叶子节点。
在InnoDB存储引擎中,对段的管理都是由引擎自身所完成,DBA不也没有必要对其进行控制。这从一定程度上简化了DBA对于段的管理。
段其实不对应表空间中某一个连续的物理区域,而是一个逻辑上的概念,由若干个零散的页面以及一些完整的区组成。
4.3 为什么有碎片区
默认情况下,一个使用InnoDB存储引擎的表只有一个聚簇索引,一个索引会生成2个段,而段是以区为单位申请存储空间的,一个区默认占用1M (64*16Kb = 1024Kb)存储空间,所以默认情况下一个只存了几条记录的小表也需要2M的存储空间么?以后每次添加一个索引都要多申请2M的存储空间么? 这对于存储记录比较少的表简直是天大的浪费。这个问题的症结在于到现在为止我们介绍的区都是非常 纯粹
的,也就是一个区被整个分配给某一个段,或者说区中的所有页面都是为了存储同一个段的数据而存在的,即使段的数据填不满区中所有的页面,那余下的页面也不能挪作他用。
为了考虑以完整的区为单位分配给某个段对于 数据量较小
的表太浪费存储空间的这种情况,lnnoDB提出了一个 碎片(fragment)区
的概念。在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的,比如有些页用于段A,有些页用于段B,有些页甚至哪个段都不属于。碎片区直属于表空间
,并不属于任何一个段。
所以此后为某个段分配存储空间的策略是这样的:
- 在刚开始向表中插入数据的时候,段是从某个碎片区以单个页面为单位来分配存储空间的
- 当某个段已经占用了
32个碎片区
页面之后,就会申请以完整的区为单位来分配存储空间
所以现在段不能仅定义为是某些区的集合,更精确的应该是 某些零散的页面
以及 一些完整的区
的集合
4.4 区的分类
- 空闲的区(FREE):现在还没有用到这个区中的任何页面
- 有剩余空间的碎片区(FREE_FRAG):表示碎片区中还有可用的页面
- 没有剩余空间的碎片区(FULL_FRAG):表示碎片区中的所有页面都被使用,没有空闲页面
- 附属于某个段的区(FSEG): 每一个索引都可以分为叶子节点段和非叶子节点段
处于 FREE
、FREE_FRAG
以及 FULL_FRAG
这三种状态的区都是独立的,直属于表空间。而处于 FSEG
状态的区是附属于某个段的
如果把表空间比作是一个集团军,段就相当于师,区就相当于团。一般的团都是隶属于某个师的,就像是处于 FSEG 的区全都隶属于某个段,而处于 FREE、FREE_FRAG 以及 FULL_FRAG 这三种状态的区却直接隶属干表空间,就像独立团直接听命于军部一样。
5. 表空间
表空间可以看做是InnoDB存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。
表空间是一个 逻辑容器
,表空间存储的对象是段,在一个表空间中可以有一个或多个段,但是一个段只能属于一个表空间。表空间数据库由一个或多个表空间组成,表空间从管理上可以划分为 系统表空间
(Systemtablespace) 、独立表空间
(File-per-table tablespace) 、撤销表空间
(Undo Tablespace) 和 临时表空间
(Temporary Tablespace) 等。
5.1 独立表空间
独立表空间,即每张表有一个独立的表空间,也就是数据和索引信息都会保存在自己的表空间中。独立的表空间(即:单表)可以在不同的数据库之间进行 迁移
。
空间可以回收(DROP TABLE 操作可自动回收表空间,其他情况,表空间不能自己回收)。如果对于统计分析或是日志表,删除大量数据后可以通过: alter table TableName engine=innodb;
回收不用的空间。对于使用独立表空间的表,不管怎么删除,表空间的碎片不会太严重的影响性能,而且还有机会处理。
独立表空间结构:由段、区、页组成。
真实表空间对应的文件大小:到数据目录里看,会发现一个新建的表对应的 .ibd 文件只占用了 96K
,才6个页面大小(MySQL5.7中),这些.ibd文件是 自扩展的
,随着表中数据的增多,表空间对应的文件也逐渐增大
5.2 系统表空间
系统表空间的结构和独立表空间基本类似,只不过由于整个MySQL进程只有一个系统表空间,在系统表空间中会额外记录一些有关整个系统信息的页面,这部分是独立表空间中没有的。
InnoDB数据字典,SYS_TABLES等SYS开头的部分表,但用户并不能直接访问这些表,而是存储引擎在启动时会把SYS_等表的数据填充到information_schema数据库中以INNODB_SYS开头的表。
相关文章:

【MySQL索引与优化篇】InnoDB数据存储结构
文章目录 1. 数据库的存储结构:页1.1 磁盘与内存交互基本单位:页1.2 页结构概述1.3 页的上层结构 2. 页的内部结构3. InnoDB行格式(或记录格式)3.1 Compact行格式3.2 Dynamic和Compressed行格式3.3 Redundant行格式 4. 区、段与碎片区4.1 为什么要有区?4.2 为什么要…...

Go学习第十二章——Go反射与TCP编程
Go反射与TCP编程 1 反射1.1 基本介绍1.2 快速入门1.3 注意事项和细节说明1.4 最佳实践 2 Tcp Socket编程2.1 基本介绍2.2 入门案例2.3 服务器监听2.4 服务器接受客户端消息 1 反射 1.1 基本介绍 **反射:**在编译时静态类型语言中实现动态特性的一种机制。 Go语言…...

uniapp编译微信小程序富文本rich-text的图片样式不生效原因
this.detail.contents this.detail.contents.replace(/\<img/gi, <img style"display:block;max-width:90%;height:auto;border:2px solid #eee;box-shadow:5px 5px 5px rgba(100,100,100,0.8);margin-bottom:10px;text-align:center;" );开始采用这个replace…...

Django实战项目-学习任务系统-任务管理
接着上期代码框架,开发第3个功能,任务管理,再增加一个学习任务表,用来记录发布的学习任务的标题和内容,预计完成天数,奖励积分和任务状态等信息。 第一步:编写第三个功能-任务管理 1࿰…...

ubuntu18.04设置开机自动启动脚本(以自动启动odoo命令行为例讲解)
简介 ubuntu作为服务器使用时,常常需要在机器重启时能自动启动我们开发的服务。 Ubuntu 16.10开始不再使用initd管理系统,改用systemd,包括用systemctl命令来替换了service和chkconfig的功能。 systemd 默认读取 /etc/systemd/system 下的配…...

golang工程——grpc-gateway 转发http header中自定义字段到grpc上下文元数据
http header 转发到 grpc上下文 grpc网关可以将请求体内容转发到grpc对应消息中。那如何获取http header头中的信息,本文将介绍如何将http header转发到grpc上下文并采用拦截器,获取http header中的内容。 有些http header中的内置字段是会转发的比如Au…...

CPU眼里的C/C++: 1.3 汇编级单步调试函数执行过程
1. 目的 2. 基于 GDB 的汇编级单步调试 原始代码 #include <stdio.h>long test() {long a 1;a 2;return a; }int main() {int ret test();printf("test return %d\n", ret);return 0; }关键 gdb 命令 si 指令执行汇编级的单步调试info registers 读取寄…...

数据结构时间复杂度(补充)和空间复杂度
Hello,今天事10月27日,距离刚开始写博客已经过去挺久了,我也不知道是什么让我坚持这么久,但是学校的课真的很多,很少有时间多出来再学习,有些科目马上要考试了,我还不知道我呢不能过哈哈哈&…...

Mac-postman存储文件目录
今天postman弹窗要求登录账号才可访问之前的API文档数据。 但是这postman的账号又是前同事的账号,我没有他的账号和密码啊。 登录了我自己的postman账号后,所有的api文档都不见了....我服了。 首先去屏幕左上角---> 前往 --->个人 然后键盘按显…...

JAVA面试题简单整理
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、重载和重写的区别一、&和&&的区别一、get和post请求的区别 delete、put一、cookie和session的区别一、Autowired和Resource区别一、”和equals…...

dd命令用法学习,是一个功能强大的工具
dd 命令是一个功能强大的工具,它有许多参数可以用来控制其行为。以下是 dd 命令中常用的一些参数: - ifinputfile:指定输入文件的路径。 - ofoutputfile:指定输出文件的路径。 - bssize:设置每个块的大小。可以使用不同…...

Games104现代游戏引擎笔记 网络游戏进阶架构
Character Movement Replication 角色位移同步 玩家2的视角看玩家1的移动是起伏一截一截,并且滞后的 interpolation:内插值,在两个旧的但已知的状态计算 extrapolation:外插值,本质是预测 内插值:但网络随着…...

Apollo 快速上手指南:打造自动驾驶解决方案
快速上手 概述云端体验登录云端仿真环境 打开DreamView播放离线数据包PNC Monitor 内置的数据监视器cyber_monitor 实时通道信息视图福利活动 主页传送门:📀 传送 概述 Apollo 开放平台是一个开放的、完整的、安全的平台,将帮助汽车行业及自…...

C现代方法(第14章)笔记——预处理器
文章目录 第14章 预处理器14.1 预处理器的工作原理14.2 预处理指令14.3 宏定义14.3.1 简单的宏14.3.2 带参数的宏14.3.3 #运算符14.3.4 ##运算符14.3.5 宏的通用属性14.3.6 宏定义中的圆括号14.3.7 创建较长的宏14.3.8 预定义宏14.3.9 C99中新增的预定义宏14.3.10 空的宏参数(C…...

Kafka KRaft模式探索
1.概述 Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者在网站中的所有动作流数据。其核心组件包含Producer、Broker、Consumer,以及依赖的Zookeeper集群。其中Zookeeper集群是Kafka用来负责集群元数据的管理、控制器的选举等。 2.内容…...

LVS-keepalived实现高可用
概念: 本章核心: Keepalived为LVS应运而生的高可用服务。LVS的调度无法做高可用,预算keepalived这个软件,实现了调度器的高可用。 但是:Keeplived不是专门为LVS集群服务的,也可以做其他服务器的高可用 LVS…...

Linux内核驱动开发的需要掌握的知识点
Linux内核驱动开发是一项复杂而有挑战性的任务,需要掌握多方面的知识和技能。下面是一些需要掌握的关键知识点,这些知识将有助于你成功地开发Linux内核驱动程序。 1. Linux内核基础知识 首先,了解Linux内核的基础知识至关重要。这包括Linux…...

nginx 动静分离 防盗链
一、动静分离环境准备静态资源配置(10.36.192.169)安装nginx修改配置文件重启nginx 动态资源配置(192.168.20.135)yum安装php修改nginx配置文件重启nginx nginx代理机配置(192.168.20.134)修改nginx子自配置文件重启nginx 客户端访问 二、防盗链nginx防止…...

MYSQL(索引篇)
一、什么是索引 索引是一种数据结构,它用来帮助MYSQL更高效的获取数据 采用索引可以提高数据检索的效率,降低IO成本 通过索引对数据排序,降低数据排序成本,降低CPU消耗 常见的有:B树索引、B树索引、哈希索引。其中Inno…...

Java API访问HDFS
一、下载IDEA 下载地址:https://www.jetbrains.com/idea/download/?sectionwindows#sectionwindows 拉到下面使用免费的IC版本即可。 运行下载下来的exe文件,注意安装路径最好不要安装到C盘,可以改成其他盘,其他选项按需勾选即可…...

高三高考免费试卷真题押题知识点合集
发表于安徽 温馨提示:有需要的真题试卷可联系本人,百卷内上免费资源。 感觉有用的下方三连,谢谢 。 免费版卷有6-60卷每卷平均4-30页 高三免费高三地理高三英语高三化学高三物理高三语文高三历史高三政治高三数学高三生物 付费版卷有1…...

css 计算函数属性:calc() 不起效 原因
踩坑:注意事项(- 减号或加号前后需要空格!!!) calc(100% - 251px); 这里错误写法中-两边没加空格,导致width不生效。但并不是所有运算符间都需要加空格,只有 和 - 需要加空格,因为运算允许负…...

2、TB6600驱动器介绍【51单片机控制步进电机-TB6600系列】
摘要:本节介绍TB6600驱动器界面及关键参数设置 一、驱动器功能界面 二、关键参数 输入电压:DC9-42V 输出电流:0.5-4A 最大功耗:160W 细分设置:1,2/A,2/B,4,8,16,32 工作温度:-10~45C 信号口驱动电流&…...

Vue3:将表格数据下载为excel文件
需求 将表格数据或者其他形式的数据下载为excel文件 技术栈 Vue3、ElementPlus、 实现 1、安装相关的库 下载xlsx 和 file-saver 库 npm install -S file-saver npm install -S xlsx引入XLSX库和FileSaver库 import XLSX from xlsx; import FileSaver from file-saver;…...

vue+Fullcalendar
vueFullcalendar: vueFullcalendar项目代码https://gitee.com/Oyxgen404/vue--fullcalendar.git...

Spring定时任务+webSocket实现定时给指定用户发送消息
生命无罪,健康万岁,我是laity。 我曾七次鄙视自己的灵魂: 第一次,当它本可进取时,却故作谦卑; 第二次,当它在空虚时,用爱欲来填充; 第三次,在困难和容易之…...

C语言学习笔记(六):数组(1)
0,问题的引入 怎么保存一个学生的成绩 float a; 怎么保存一个班(10人)的学生的成绩 float a,b,c,d......; float a1,a2,a3,........; 这样太麻烦了 -》“数组” 1,数组 什么是数组ÿ…...

apk反编译修改教程系列-----修改apk中的图片 任意更换apk桌面图片【三】
往期教程: apk反编译修改教程系列-----修改apk应用名称 任意修改名称 签名【一】 apk反编译修改教程系列-----任意修改apk版本号 版本名 防止自动更新【二】 这次实例演示下如何更换apk安装后的桌面图标图片。其实这个步骤前面我有一个教程贴。这次针对步骤做个补…...

【IO面试题 五】、 Serializable接口为什么需要定义serialVersionUID变量?
文章底部有个人公众号:热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享? 踩过的坑没必要让别人在再踩,自己复盘也能加深记忆。利己利人、所谓双赢。 面试官: Serializable接口为什么…...

san.js源码解读之模版解析(parseTemplate)篇——readIdent函数
一、源码分析 /*** 读取ident* 这里的 ident 指标识符(identifier),也就是通常意义上的变量名* 这里默认的变量名规则为:由美元符号($)、数字、字母或者下划线(_)构成的字符串** inner* param {Walker} walker 源码读取对象* return {string}*/ functio…...