MySQL记录删除后竟能按中间被删除的主键加回去,磁盘空间被重用!——底层揭秘MySQL行格式记录头信息

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS Agent(兼容OpenClaw),2核4GB
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 当Mysql记录被删除,页中记录存储结构如何变化?当删除的记录再次被插入,页中记录存储结构如何变化?本篇讲解记录头信息的底层原理和计算,让你从原理理解它!

说在前面——本篇也是我读书总结笔记,因为是讲底层原理,我个人认为本文难度是相当高的,可能需要一定的基础。

我将文中的图片包含的计算原理全都演算了一遍,因为书本没有过程详细计算,欢迎大家阅读和仔细推敲。

如果是大忙人,也可以跳过计算,看懂过程和大致原理即可。

1.记录头信息有什么用?

  记录头信息里面有很多属性,最容易理解的就是next_record指针,单链表都会有next指针,这样才会找得到下一个结点,这对于页中的每条记录也是一样,上一条记录需要知道下一条记录在哪里。

  上一篇说到了innodb行格式,重点讲了一下dynamic行格式,知道一条记录实际存储如下图。没办法,说到底层原理如果不看上一篇文章是不可能完全理解的,耶稣来了也没法一篇说明白,见这里MySQL的varchar水真的太深了——InnoDB记录存储结构,必须记住下图的上面行格式部分,每条记录不仅是记录的真实数据,还有记录的额外信息。

如果你目前只是为了开发学习本部分知识,那么可以直接跳到本篇第4、5节。传送-->

  在utf8mb4字符集中,能用0~4字节表示一个字符,像varchar这种变长类型和char这种定长类型实际占用的字节数都会被记录到变长字段列表

  如果字段没有被NOT NULL限定,那么就允许为NULL,该列就会有NULL值列表

  关于记录头信息,下面这个表先列出来,往后面看的时候不理解时可以返回查看这个表,方便理解。

名称 大小(单位:bit) 描述
预留位1 1 没有使用
预留位2 1 没有使用
delete_mask 1 标记该记录是否被删除
min_rec_mask 1 B+树的每层非叶子节点中的最小记录都会添加该标记
n_owned 4 表示当前记录拥有的记录数
heap_no 13 表示当前记录在记录堆的位置信息
record_type 3 表示当前记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示Infimum记录,3表示Supremum记录
next_record 16 表示本条记录真实数据部分到下一条记录真实数据的距离

从表中所说可以看到,记录头信息一共是40bit就是5个字节

2.记录在页中的存储结构

  innodb管理存储空间的基本单位,一个页的大小默认是16KB,插入的记录会按照指定的行格式(默认dynamic)存储到User Records部分。但是在一开始生成页的时候,其实并没有User Records这个部分,每当我们插入一条记录,都会从Free Space部分(也就是尚未使用的存储空间) 申请一个记录大小的空间,并将这个部分划分到User Records部分,当Free Space部分的空间全部被User Records部分替代掉之后,也就意味着这个页使用完了,如果还有新的记录插入的话,就需要去申请新的页了。

有人会疑问了,图中这个Infimum+Supremum是什么?

   Infimum记录 的下一条记录就是本页中主键值最小的用户记录,而本页中主键值最大的用户记录的下一条记录就是 Supremum记录。但是这两条记录不在User Records部分,是单独占用的空间,可结合上一张图理解。都是由5个字节的记录头和8个字节的一个固定单词组成,如下所示

3.记录头信息的底层原理和计算(show time,难度搞起来)

首先,建个表record_test

CREATE TABLE record_test(
 c1 INT,
 c2 INT,
 c3 VARCHAR(10000),
  PRIMARY KEY (c1)
) CHARSET=utf8mb4;

执行插入语句

INSERT INTO record_test VALUES(1, 100, 'aa你好'), (2, 200, 'bb哈哈'), (3, 300, 'cc来了'), (4, 400, 'dd在哪');

最终表数据如下

4条记录的存储结构示意图如下

注意:
1.图中画记录的时候只选取了记录头的一部分,省略了变长列表和NULL值列表,但是实际计算的时候要带上。
2.这里把隐藏列省略了,归并到 "其他信息" 里面了

  看到这里,你一定和我有着相同的疑问,为什么next_record显示36,它表示本条记录真实数据部分到下一条记录真实数据的距离。这个怎么计算的呢?

  现在我来和你说说底层那些不为人知的东西。要知道,记录的真实数据除了所有的数据列之外,MySQL还会为每条记录默认添加一些列(也称为隐藏列),隐藏列也包含在记录的真实数据部分,如下
| 列名|是否必须 | 占用空间 |描述 |
|--|--|--|--|
|DB_ROW_ID| 否 |6字节 | 行ID,唯一标识一条记录 |
| DB_TRX_ID| 是| 6字节 | 事务ID |
| DB_ROLL_PTR| 是| 7字节| 回滚指针 |

  InnoDB表对主键的生成策略:优先使用用户自定义主键作为主键,如果用户没有定义主键,则选取一个Unique键作为主键(必须NOT NULL不允许存NULL),如果表中连Unique键都没有定义的话,则InnoDB会为表默认添加一个名为DB_ROW_ID的隐藏列作为主键。

  从上表中可以看出:InnoDB存储引擎会为每条记录都添加 DB_TRX_IDDB_ROLL_PTR这两个列,但是 DB_ROW_ID是可选的(在没有自定义主键以及不允许存NULL值的Unique键的情况下才会添加该列)。这些隐藏列的值不用我们操心,InnoDB存储引擎会自己帮我们生成的。

所以刚刚next_record36字节的计算方法就是
6+7(隐藏列2个,因为有自定义主键)=13字节
4(int长度)+4(int长度)+8(变长varchar实际字节数)=16字节
下一列记录的额外信息(变长列表+NULL值列表+记录头)
1+1+5=7字节
总共13+16+7=36

注意,图中画记录的时候只选取了记录头的一部分,计算的时候直接记录头按5字节计算,加上变长列表长度和NULL值列表长度即可。

如果变长列表NULL值列表不知道怎么计算长度,见上一篇MySQL的varchar水真的太深了——InnoDB记录存储结构,不看上篇不可能理解的。

而且你可能会疑问为什么第4条记录的下一条却要-123字节?

  前面说过,最大记录的下一条记录是Supremum记录,而Infimum记录的heap_no0,而Supremum记录的heap_no1,存放位置是在所有记录之前,最小记录的heap_no是从2开始的。前面给大家看过记录在页中的存储结构,知道InfimumSupremum记录在User Records之前。

  所以最大记录的下一条就是要找到Supremum记录,那么就要往回走3条记录和第一条记录的最小记录变长列表+NULL值列表+头信息(共7字节),然后加上Supremum真实数据部分的固定8个字节。

36*3+7+8=123字节

  所以为第4条记录的next_record-123,代表指针往前走123字节就是下一条记录的真实数据部分的地址。


如果你还细致的观察到Infimum记录的next_record28,我觉得你挺适合做研究。

  在存储结构上,Infimum记录后面是Supremum记录,接着才是第一条数据记录。
  逻辑上,Infimum下一条记录是第一条数据记录,所以计算方法是
8(Infimum固定字节) + 5(Supremum记录头) + 8(Supremum固定字节) + 7(第一条数据记录的变长字段列表+NULL值列表+记录头) = 28字节。

  为了更形象的表现next_record的作用,我们用箭头来替代next_record的值,注意箭头指向的位置,都是指向真实数据开始的地址。

你可能会疑问,为啥要next_record指向记录头信息和真实数据之间的位置呢?指向整条记录的开头位置不好吗?

  因为这个位置刚刚好,向左读取就是记录头信息,向右读取就是真实数据。我们前边还说过变长字段长度列表、NULL值列表中的信息都是逆序存放,这样可以使记录中位置靠前的字段和它们对应的字段长度信息在内存中的距离更近,可能会提高高速缓存的命中率。

4.当记录被删除,页中记录存储结构如何变化?

当然最大的疑问就是被删除的记录还在页中么?
  是的,你以为记录删除了,可它还在真实的磁盘上(占用空间依然存在)。这些被删除的记录之所以不从磁盘上移除,是因为移除它们之后,还需要再磁盘中重新排序其他记录,这会带来一定的性能损耗,所以只是打一个删除标记就可以避免这个问题,首先deleted_mask设置为1,然后被删除掉的记录加入到垃圾链表,记录在这个链表中占用的空间称为可重用空间,之后如果有新记录插入到表中的话,它们就可能覆盖掉被删除的这些记录占用的空间。

来演示一下

delete from record_test where c1 = 2;

发现第二条记录被删了

在内存中是怎么样的呢?

删除第2条记录变化如下

  • 2条记录并没有从存储空间中移除,而是把该条记录的delete_mask值设置为1
  • 2条记录的next_record值变为了0,意味着该记录没有下一条记录了。
  • 1条记录的next_record指向了第3条记录。
  • 最大记录的n_owned值从5变成了4,因为除了自身Supremum记录外,还有3条数据记录(注:Infimumn_owned1是因为包含自身算一条记录)

  无论怎么对页中的数据进行增删改操作,InnoDB始终会维护记录的一个单向链表,链表中的各个节点是按照主键值从小到大的顺序链接起来的。

5.当删除的记录再次被插入,页中记录存储结构如何变化?

INSERT INTO record_test VALUES(2, 200, 'bb哈哈');

可以看到,刚刚删除的第二条数据又回来了

内存结构变化如下

  InnoDB并没有因为新记录的插入而为它申请新的存储空间,而是直接复用了原来被删除记录的存储空间。

  当数据页中存在多条被删除掉的记录时,这些记录的next_record属性将会把这些被删除掉的记录组成一个垃圾链表,以备之后重用这部分存储空间。


本篇总结:
  本篇主要讲了Infimum+Supremum部分,分别是页中最小记录的前一个和最大记录的后一个记录,User Records部分使我们插入的真实数据部分,Free Space是页总尚未使用的部分。然后讲解了图中next_record指针地址的计算。
  我们知道,页中的记录是单链表,页与页之间是双向链表,其实每个数据页的File Header部分有上一页和下一页的编号,所以所有数据页会组成一个双向链表。



欢迎一键三连~



有问题请留言,大家一起探讨学习



----------------------Talk is cheap, show me the code-----------------------

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
目录
相关文章
|
10月前
|
存储 关系型数据库 MySQL
使用命令行cmd查询MySQL表结构信息技巧分享。
掌握了这些命令和技巧,您就能快速并有效地从命令行中查询MySQL表的结构信息,进而支持数据库维护、架构审查和优化等工作。
795 9
|
关系型数据库 MySQL
MySQL查看连接数和进程信息
这篇文章介绍了如何在MySQL中查看连接数和进程信息,包括当前打开的连接数量、历史成功建立连接的次数、连接错误次数、连接超时设置,以及如何查看和终止正在执行的连接进程。
2001 10
|
存储 缓存 关系型数据库
MySQL为什么需要主键
本文介绍了MySQL中主键的重要性及最佳实践。主键用于唯一标识表中的每一行,其值必须唯一且不允许为空。主键有助于简化更新和删除操作,避免影响无关行。推荐使用与业务无关的自增ID作为Innodb表的主键,以优化存储结构、减少碎片并提高性能。此外,文章还提到不更新、不重用主键值以及避免使用可能变更的字段(如邮箱)作为主键的良好习惯。最后强调了紧凑索引结构对查询效率的关键作用。
394 0
MySQL为什么需要主键
|
存储 关系型数据库 MySQL
MySQL主键谁与争锋:MySQL为何钟爱自增主键ID+UUID?
本文深入探讨了在MySQL中使用自增类型主键的优势与局限性。自增主键通过保证数据的有序性和减少索引维护成本,提升了查询和插入性能,简化了数据库管理和维护,并提高了数据一致性。然而,在某些业务场景下,如跨表唯一性需求或分布式系统中,自增主键可能无法满足要求,且存在主键值易预测的安全风险。因此,选择主键类型时需综合考虑业务需求和应用场景。
577 2
|
存储 关系型数据库 MySQL
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
覆盖索引、前缀索引、索引下推、SQL优化、EXISTS 和 IN 的区分、建议COUNT(*)或COUNT(1)、建议SELECT(字段)而不是SELECT(*)、LIMIT 1 对优化的影响、多使用COMMIT、主键设计、自增主键的缺点、淘宝订单号的主键设计、MySQL 8.0改造UUID为有序
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
|
存储 SQL 关系型数据库
MySQL 存储过程错误信息不打印在控制台
MySQL 存储过程错误信息不打印在控制台
372 1
|
存储 SQL 关系型数据库
mysql中主键索引和联合索引的原理与区别
本文详细介绍了MySQL中的主键索引和联合索引原理及其区别。主键索引按主键值排序,叶节点仅存储数据区,而索引页则存储索引和指向数据域的指针。联合索引由多个字段组成,遵循最左前缀原则,可提高查询效率。文章还探讨了索引扫描原理、索引失效情况及设计原则,并对比了InnoDB与MyISAM存储引擎中聚簇索引和非聚簇索引的特点。对于优化MySQL性能具有参考价值。
|
存储 关系型数据库 MySQL
MySQL 如何存储地理信息
MySQL 如何存储地理信息
1895 1
|
存储 SQL 关系型数据库
mysql百分数转小数点格式
在MySQL中,将百分数转换为小数点格式是一个简单直接的操作,可以通过基本的数学表达式和函数实现。无论是处理以字符串形式存储的百分数值,还是直接以数值形式表示的百分比,都可以通过适当的转换查询轻松实现这一目标。通过理解和应用这些基本的转换方法,可以有效地处理和分析数据库中的百分比数据。
578 5
|
SQL 关系型数据库 MySQL
mysql编写sql脚本:要求表没有主键,但是想查询没有相同值的时候才进行插入
mysql编写sql脚本:要求表没有主键,但是想查询没有相同值的时候才进行插入
207 0

推荐镜像

更多