• 一条 INSERT 背后的秘密:揭开 InnoDB 记录结构的神秘面纱

一条 INSERT 背后的秘密:揭开 InnoDB 记录结构的神秘面纱

2025-06-16 10:00:05 栏目:宝塔面板 46 阅读

你以为你写了一条 SQL,其实你是在和数据库的一整套存储机制打交道。

一、引言:懂记录结构,真的很重要!

开篇三连击:

  • 当你敲下INSERT时,数据是在磁盘「盖房子」还是「搭积木」?
  • 行格式里藏着哪些「加密代码」?
  • 一条“莫名其妙”的慢查询,根源可能是行格式选错?

这些问题的答案,就藏在 InnoDB 的记录结构里。

我们在写 SQL 的时候,经常只关注“写对了没”、“跑起来没报错”。但真正理解 MySQL 的底层行为,往往要从一句简单的 INSERT 开始。

二、从一条INSERT语句开始说起

当我们执行INSERT INTO users (name, age,address) VALUES ('张三', 25,'北京.海淀');这条语句时,数据并不会直接 “一股脑” 地塞进磁盘。InnoDB 会按照特定的规则,将数据 “搭建” 成特定的结构,然后再存储到磁盘上。

为了更好地理解这个过程,我们先来对比一下行数据以及行结构。

假设我们有一张users表,包含id、name、age、address四个字段。

CREATE TABLEusers (
    idINTUNSIGNEDNOTNULL AUTO_INCREMENT COMMENT'主键ID',
    nameVARCHAR(100) NOTNULLCOMMENT'用户姓名',
    age INTUNSIGNEDCOMMENT'年龄',
    address VARCHAR(255) COMMENT'地址',
    PRIMARY KEY (id)
) ENGINE=InnoDBDEFAULTCHARSET=utf8mb4 COMMENT='用户信息表';

当我们插入一条数据(1, '张三', 25,'北京.海淀')时,从表面上看,我们看到的行数据是这样的:

id

name

age

address

1

张三

25

北京海淀

然而在 InnoDB 中,一条数据并非简单存储,而是拆分成多个部分:记录头信息保存元数据,变长字段列表记录变长字段及长度,NULL 值列表标记哪些字段为 NULL。

InnoDB目前支持四种行格式:

  • COMPACT(最常用)
  • REDUNDANT(MySQL 5.0之前)
  • DYNAMIC(MySQL 5.7默认)
  • COMPRESSED(压缩格式)

COMPACT 行格式是最常用的“标准模板”,掌握它能帮助你理解 InnoDB 记录结构的核心。

三、COMPACT 行格式详细介绍:数据存储的 “标准模板”

废话不多说,直接看图:

1. 记录头信息:数据的 “身份证”

记录头信息仅占5字节,却包含记录类型、删除标记、B+树位置等关键信息,是记录的重要标识。

当我们执行DELETE语句删除一条记录时,InnoDB 并不会立即从磁盘上删除这条记录,而是在记录头信息中设置删除标记,后续再通过专门的机制进行清理。

字段 (Field)

位数 (Bits)

描述 (Description)

预留位1

1

保留位,目前没有用到。

预留位2

1

保留位,目前没有用到。

delete_mask

1

标记该记录是否被删除,1-是,0-否

min_rec_mask

1

标记该记录是否是B+树叶子节点中最小的记录,1-是,0-否

record_type

3

标记记录的类型。000表示普通记录,001表示最小值记录,010表示目录记录,011表示最大值记录。

n_owned

4

表示当前记录拥有的记录数

heap_no

13

标记当前记录在当前页面(Page)中的相对位置(槽号)。

next_record

16

表示下一条记录的相对位置

预留位2

1

保留位,目前没有用到。

2. 变长字段列表:应对 “变化多端” 的数据

在实际应用中,很多字段的数据长度是不固定的,比如VARCHAR、TEXT、BLOB等类型的字段。变长字段列表就是为了应对这些 “变化多端” 的数据而设计的。它会记录哪些字段是变长的,以及它们的长度。

变长字段列表采用倒排的方式存储,也就是说,它从右往左存储每个变长字段的长度。

(1) 变长字段列表如何存储实际数据?

当执行插入语句INSERT INTO users VALUES(1, '张三', 25, '北京.海淀');时,我们来分析一下变长字段列表的存储方式。

① 字段分析

  • id:INT 类型,固定长度 4 字节,不属于变长字段
  • name:VARCHAR (100),实际存储 ' 张三 ',UTF-8 编码下每个汉字占 3 字节,共 6 字节
  • age:INT 类型,固定长度 4 字节,不属于变长字段
  • address:VARCHAR (255),实际存储 ' 北京.海淀 ',共包含 5 个字符(2 个汉字、1 个点、2 个汉字),每个汉字 3 字节,点 1 字节,共 13 字节

② 变长字段列表的倒排存储

上面的例子中,有两个变长字段:name和address。它们的长度分别是 6 字节和 13 字节。根据倒排存储规则,变长字段列表会按照从右到左的顺序记录这些长度。

  • 表定义顺序是:name, address
  • 倒排后,存的时候顺序是:address, name

因此,变长字段列表的内容为:[13,6]。

这些值并不是直接以十进制存入,而是编码成 1~2 字节的二进制形式(依字段长度大小决定)。

3. NULL 值列表:节省空间的 “小能手”

在 InnoDB 中,NULL 值列表是一种节省空间的巧妙设计。它不存储 NULL 的实际值,而是用每个字段对应的一位二进制位来标记:

  • 1 表示该字段为 NULL;
  • 0 表示不为 NULL。

在计算 InnoDB 记录结构中的 NULL 值列表时,只有那些“允许为 NULL”的字段才会被纳入统计。

以 users 表为例:

  • id 是主键,不能为 NULL;
  • name 被 NOT NULL 明确声明,也不能为 NULL;
  • age 和 address 没有限定 NOT NULL,默认是可以为 NULL 的。

所以,NULL 值列表中只包含 age 和 address 这两个字段的状态位。

NULL 值列表的位顺序,是按照表结构中允许 NULL 字段的出现顺序排列的,且仅包含这些字段。

四、行溢出:当数据太大时会发生什么?

1. 为什么会出现行溢出?

InnoDB 的数据存储以 “页” 为基本单位,每页默认大小为 16KB。当我们插入的数据(如一篇几万字的文章、高清图片的二进制数据)长度超过一页能容纳的空间时,InnoDB 就会遇到 “空间不够用” 的难题。就像你想把 100 本书塞进只能装 50 本书的箱子,自然装不下。

2. 什么是行溢出?

为了解决上述问题,InnoDB 引入了行溢出机制:

  • 当数据过长时,它会把超出数据页容量的部分 “搬” 到额外的溢出页中存储;
  • 并在原数据页保留一个指向溢出页的指针(通常是20字节)。

这就好比把装不下的书先放在旁边的临时箱子,再在原本的箱子贴上标签注明 “其余书在隔壁箱”。

行溢出虽解决大字段存储,但带来性能隐患,如查询慢、空间管理复杂、碎片增多。优化可从多方面入手:拆大字段表、选适配数据类型与行格式,控制字段长度,同时避免在大字段建索引,以此提升数据库性能。

五、结语:深入底层,才能掌控全局

数据库的 “高性能密码” 藏在底层结构里。从 INSERT 语句到磁盘存储,COMPACT 行格式的字段管理、行溢出的优化逻辑,都是提升数据库能力的关键。懂记录结构,才能在面试中从容应答,在优化时直击痛点。

记住:深挖底层原理,才能让技术成长 “知其所以然”。欢迎留言交流,一起解锁更多数据库奥秘!

本文地址:https://www.yitenyun.com/289.html

搜索文章

Tags

数据库 API FastAPI Calcite 电商系统 MySQL Web 应用 异步数据库 数据同步 ACK 双主架构 循环复制 Deepseek 宝塔面板 Linux宝塔 Docker 生命周期 JumpServer JumpServer安装 堡垒机安装 Linux安装JumpServer 序列 核心机制 esxi esxi6 root密码不对 无法登录 web无法登录 Windows Windows server net3.5 .NET 安装出错 宝塔面板打不开 宝塔面板无法访问 SSL 堡垒机 跳板机 HTTPS 查看硬件 Linux查看硬件 Linux查看CPU Linux查看内存 无法访问宝塔面板 连接控制 机制 HTTPS加密 Windows宝塔 Mysql重置密码 ES 协同 HexHub Serverless 无服务器 语言 Oracle 处理机制 运维 技术 响应模型 Spring SQL 动态查询 索引 查询 Redis 异步化 OB 单机版 scp Linux的scp怎么用 scp上传 scp下载 scp命令 电商 系统 group by 缓存方案 缓存架构 缓存穿透 存储 自定义序列化 分页查询 数据 主库 InnoDB 数据库锁 监控 PostgreSQL 工具 开源 存储引擎 日志文件 MIXED 3 Rsync 架构 修改DNS Centos7如何修改DNS RocketMQ 长轮询 配置 流量 服务器 管理口 防火墙 黑客 Linux 安全 R edis 线程 • 索引 • 数据库 高可用 聚簇 非聚簇 MySQL 9.3 ​Redis 机器学习 推荐模型 sftp 服务器 参数 AI 助手 SQLite-Web SQLite 数据库管理工具 信息化 智能运维 SQLark 缓存 核心架构 订阅机制 业务 GreatSQL 连接数 同城 双活 人工智能 向量数据库 推荐系统 prometheus Alert 优化 万能公式 云原生 RDB AOF Doris SeaTunnel MVCC 共享锁 线上 库存 预扣 PG DBA Ftp 加密 场景 数据备份 Postgres OTel Iceberg Redisson 锁芯 redo log 重做日志 Python 模型 网络架构 网络配置 Canal 向量库 Milvus INSERT COMPACT 高效统计 今天这篇文章就跟大家 B+Tree ID 字段 网络故障 Recursive Redis 8.0 事务 Java 开发 微软 SQL Server AI功能 大模型 Web IT运维 引擎 性能 不宕机 SVM Embedding Hash 字段 自动重启 数据脱敏 加密算法 窗口 函数 Netstat Linux 服务器 端口 分库 分表 MongoDB 容器 sqlmock 虚拟服务器 虚拟机 内存 Entity openHalo Undo Log 数据类型 LRU 崖山 新版本 OAuth2 Token mini-redis INCR指令 ZODB 数据集成工具 单点故障 DBMS 管理系统 SpringAI 分布式 集中式 JOIN 读写 容器化 悲观锁 乐观锁 磁盘架构 QPS 高并发 启动故障 StarRocks 数据仓库 数据分类 filelock Testcloud 云端自动化 Redka 分布式架构 分布式锁​ 大表 业务场景 排行榜 排序 意向锁 记录锁 聚簇索引 非聚簇索引 PostGIS 原子性 分页 数据结构 EasyExcel MySQL8 Pottery R2DBC IT Caffeine CP 部署 InfluxDB 1 数据页 池化技术 连接池 AIOPS MCP 开放协议 dbt 数据转换工具 发件箱模式 Order SSH Web 接口 字典 RAG HelixDB 工具链 Go 数据库迁移 网络 对象 优化器 速度 服务器中毒 事务隔离 双引擎 单线程 仪表盘 频繁 Codis 分页方案 排版 传统数据库 向量化 LLM 日志 线程安全 订单 Crash 代码 Pump List 类型 事务同步 UUIDv7 主键 Ansible