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

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

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

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