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

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

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

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