前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >innodb是如何存数据的?yyds

innodb是如何存数据的?yyds

作者头像
Bug开发工程师
发布于 2022-01-11 06:59:20
发布于 2022-01-11 06:59:20
6760
举报
文章被收录于专栏:码农沉思录码农沉思录

前言 如果你使用过mysql数据库,对它的存储引擎:innodb,一定不会感到陌生。

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

那么,问题来了:

  1. innodb底层是如何存储数据的?
  2. 表中有哪些隐藏列?
  3. 用户记录之间是如何关联起来的?

如果你想知道上面三个问题的答案,那么,请继续往下面看。

本文主要包含如下内容:

1.磁盘or内存?

1.1 磁盘

数据对系统来说是非常重要的东西,比如:用户的身份证、手机号、银行号、会员过期时间、积分等等。一旦丢失,会对用户造成很大的影响。

那么问题来了,如何才能保证这些重要的数据不丢呢?

答案:把数据存在磁盘上。

当然有人会说,如果磁盘坏了怎么办?

那就需要备份,或者做主从了。。。

好了,打住,这不是今天的重点。

言归正传。

大家都知道,从磁盘上读写数据,至少需要两次IO请求才能完成。一次是读IO,另一次是写IO。

而IO请求是比较耗时的操作,如果频繁的进行IO请求势必会影响数据库的性能。

那么,如何才能解决数据库的性能问题呢?

1.2 内存

把数据存在寄存器?

没错,操作系统从寄存器中读取数据是最快的,因为它离CPU最近。

但是寄存器有个非常致命的问题是:它只能存储非常少量的数据,设计它的目的主要是用来暂存指令和地址,并非存储大量用户数据的。

这样看来,只能把数据存在内存中了。

因为内存同样能满足我们,快速读取和写入数据的需求,而且性能是非常可观的,只是比较寄存器稍稍慢了一丢丢而已。

不过有个让人讨厌的地方是,内存相对于磁盘来说,是更加昂贵的资源。通常情况下,500G或者1T的磁盘,是很常见的。但你有听说过有500G的内存吗?别人会以为你疯了。内存大小讨论的数量级一般是16G或32G。

内存可以存储一些用户数据,但无法存储所有的用户数据,因为如果数据量太大了,它可能还是存不下。

此外,即使用户数据能刚好存在内存,以后万一有一天,数据库服务器或者部署节点挂了,或者重启了,数据不就丢了?

怎么做,才能不会因为异常情况,而丢数据。同时,又能保证数据的读写速度呢?

2.数据页

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.用户记录

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

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

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

  1. compact行格式
  2. redundant行格式
  3. dynamic行格式
  4. compressed行格式

我们以compact行格式为例:

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

  1. 记录额外信息,它包含了变长字段、null值列表和记录头信息。
  2. 隐藏列,它包含了行id、事务id和回滚点。
  3. 真正的数据列,包含真正的用户数据,可以有很多列。

下面让我们一起了解一下这些内容。

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是怎么知道,某条记录的下一条记录是谁?

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

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

4.最大和最小记录

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

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

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

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

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

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

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

5.页目录

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

咋一看,没有问题。

但如果仔细想想。

效率会不会有点低?

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

有没有更高效的方法?

这就需要使用页目录了。

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

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

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

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

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

so easy

6.文件头部和尾部

6.1 文件头部

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

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

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

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

  1. 页号
  2. 上一页页号
  3. 下一页页号
  4. 页类型

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

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

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

6.2 文件尾部

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

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

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

这就需要用到文件尾部

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

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

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

7.页头部

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

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

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

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

它还记录了:

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

其实还有很多,在这里就不一一列举了,有兴趣的朋友可以找我私聊。

总结

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

参考:《mysql是怎样运行的》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-09-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
innodb是如何存数据的?yyds
众所周知,在mysql8以前,默认的存储引擎是:myslam。但mysql8之后,默认的存储引擎已经变成了:innodb,它是我们建表的首选存储引擎。
苏三说技术
2021/08/23
1.3K1
Mysql进阶索引篇02——InnoDB存储引擎的数据存储结构
前面我们已经剖析了mysql中InnoDB与MyISAM索引的数据结构,了解了B+树的设计思想、原理,并且介绍了B+树与Hash结构、平衡二叉树、AVL树、B树等的区别和实际应用场景。
半旧518
2022/10/26
1.3K0
Mysql进阶索引篇02——InnoDB存储引擎的数据存储结构
好好学习一下InnoDB中的页
周末没啥计划,把大佬的<MySQL是怎样运行的>又翻了出来,重新学习一下页的概念。
终有救赎
2023/11/18
2770
好好学习一下InnoDB中的页
InnoDB & index页-mysql详解(二)
上篇文章说了,mysql有character_Set_client,character_set_collection,character_Set_result来编码解码字符集。字符集有ascii、iso8859、gb2312、gbk、utf-8等。字符集和比较级的介绍。
keying
2022/12/14
5420
【MySQL】InnoDB 是如何存储数据的
InnoDB 是怎么存储数据的?数据目录 -> 聚簇索引 -> 页 -> 行格式 -> 独立表空间 -> 区,组,段 -> 系统表空间……
JuneBao
2022/10/26
6.2K0
【MySQL】InnoDB 是如何存储数据的
InnoDB在SQL查询中的关键功能和优化策略
通过上篇文章《MySQL的体系结构与SQL的执行流程》了解了SQL语句的执行流程以及MySQL体系结构中「连接器」、「SQL接口」、「解析器」、「优化器」、「执行器」的功能以及在整个流程中的作用。不过上篇文章留了个尾巴,在执行器调用存储引擎后,存储引擎内部做了什么事没有进一步说明,本文会对此展开介绍,使得我们对SQL整体的执行流程有更加清晰的认识。
王二蛋
2024/01/25
9994
Mysql原理篇之索引是如何一步步实现的---上--02
Mysql索引的实现是在存储引擎层完成的,因此本文所讲内容是以Innodb存储引擎为基础展开的,核心是讲清楚Innodb的数据存储结构。
大忽悠爱学习
2022/10/24
6740
Mysql原理篇之索引是如何一步步实现的---上--02
高并发之存储篇:关注下索引原理和优化吧!躲得过实践,躲不过面试官!
不管是啥业务,最终数据都要落地,数据库这一环是肯定少不了的。随着业务发展,并发越来越高,数据库很容易成为整个链路的短板。这也是大厂面试中比较常被问到的。而调优的第一步,都是从sql语句、索引入手。先得保证单个数据库执行没问题,才会有更高层次的分库分表、弹性、容灾等等。
Coder的技术之路
2021/05/14
8980
高并发之存储篇:关注下索引原理和优化吧!躲得过实践,躲不过面试官!
MySQL底层概述—4.InnoDB数据文件
一个表空间会包含多个段,一个段会包含多个区(256个区就是一个组);一个区又会包含64个页,一个页里面又会包含一行一行的记录Row。
东阳马生架构
2025/02/11
2140
MySQL中InnoDB及索引深入剖析
我的博客: https://www.luozhiyun.com/archives/273
luozhiyun
2020/04/24
7580
MySQL中InnoDB及索引深入剖析
InnoDB(7)数据持久化 --mysql从入门到精通(十二)
上篇文章我们说了,索引页分为7个部分,其中free space会给user recoreds分配空间存储真实数据,直到用完申请新的页。查询拥有page directory,会分成不同的槽点,最小槽点有一个值,最大槽点有1~8个值,查询的时候用二分查找法定位id的槽点,然后遍历当前槽点就好。
keying
2022/07/26
4160
深入浅出——InnoDB页结构详解,慎入!
上一篇文章对InnoDB的行格式进行了解析,但是却把记录头信息抛到这里来讲,那么开始吧,注意本片需要有一点数据结构和算法基础,如果基础薄弱,请先确保自己会二分查找和链表再来食用
Karos
2023/06/14
7780
深入浅出——InnoDB页结构详解,慎入!
一种探究 InnoDB 的存储格式的新方式
文件的存储结构包含了系统大量的实现细节,比如 java 的 class 文件结构,rocksdb 的存储结构。MySQL InnoDB 的存储格式比较复杂,但确实我们理解 MySQL 技术内幕不必可少的一环。传统的分析的方法有下面这两个
挖坑的张师傅
2022/05/13
4700
一种探究 InnoDB 的存储格式的新方式
一文理解 MySQL 中的 page 页
从InnoDB存储引擎的逻辑结构看,所有数据都被逻辑地存放在一个空间内,称为表空间(tablespace),而表空间由段(sengment)、区(extent)、页(page)组成。在一些文档中extend又称块(block)。
逆锋起笔
2021/04/26
6.4K0
一文理解 MySQL 中的 page 页
MySQL-InnoDb行格式与数据页结构 Krains 2020-08-08
在InnoDB中,数据会存储到磁盘上,在真正处理数据时需要先将数据加载到内存,表中读取某些记录时,InnoDB存储引擎不需要一条一条的把记录从磁盘上读出来,InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB,也就是说,当需要从磁盘中读数据时每一次最少将从磁盘中读取16KB的内容到内存中,每一次最少也会把内存中的16KB内容写到磁盘中。
Krains
2020/08/10
6600
扒一扒InnoDB数据在硬盘上是如何存放的
hello,小伙伴们下午好。俗话说的好,一时拖更一时爽,一直拖更一直爽。但是今天决定更一下啦。高能预警,为了还债,特地写了篇长文。
陈琛
2020/06/12
7770
扒一扒InnoDB数据在硬盘上是如何存放的
InnoDB(5)索引页 --mysql从入门到精通(十)
上篇文章介绍了行溢出,表中最多创建65535个字节,而null值列表占用一个字节,变长字段长度列表占用两个字节,所以最长是65532个字节。而varchar(M)填写多少,要根据不同的字符集来规定,比如ascii一个字符是一个字节,gbk最大是2个字节,utf8最大是3个字节。数据也会溢出,数据溢出,则是会分成若干页存储,而compact行格式,真实数据列表会780左右字节,然后存页的地址值,方便查找剩余的真是数据。Mysql5.7后默认用dynamic行格式,而dynamic行格式在行溢出的情况下真实数据列表只存储页码地址值。Redundant则是会有压缩算法压缩页码分页,更节省空间。
keying
2022/07/26
4300
数据库之索引<保姆级文章>
2.二叉搜索树: 如果我们使用二叉搜索树的中序遍历是⼀个有序数组 但有几个问题导致它不适合用作索引的数据结构: 2.1.最坏情况下时间复杂度为O(N)
用户11305962
2024/10/09
1940
数据库之索引<保姆级文章>
堂妹问我:innodb是如何插入数据的?
上周系统上线,发现数据量大的时候查询较慢。经过调查发现,负责该功能的妹妹建表时没有指定主键。
敖丙
2021/01/08
8580
堂妹问我:innodb是如何插入数据的?
从数据页和B+树的角度看索引失效原因
文章开头的面试场景不是我编出来的,兄弟们,刚毕业一两年面试的我就出现过这种问题。仅仅问你失效场景,只要准备过面试的人都能答出来。但是再往下问问,就不知道怎么答了。
小许code
2023/08/07
7230
从数据页和B+树的角度看索引失效原因
相关推荐
innodb是如何存数据的?yyds
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档