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

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

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

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