InnoDB的“身体结构”:页、Buffer Pool与Redo Log的底层奥秘

news2026/3/30 16:27:53
欢迎来到MySQL InnoDB存储引擎的“解剖室”很多人每天都在写SQL却从未见过数据在磁盘上真正的模样。当面试官问“为什么InnoDB比MyISAM快”或者“数据库宕机了数据是怎么恢复的”如果你只能回答“因为有日志”或者“因为有缓存”那还停留在“知其然”的层面。今天我们要深入到字节码和磁盘扇区的级别彻底拆解InnoDB的“身体结构”看看它是如何通过精妙的设计在速度与可靠性之间走钢丝的。物理存储结构数据在磁盘上的“俄罗斯套娃”首先我们要打破一个迷思数据库并不是把数据像流水一样随意倒在磁盘上的。InnoDB为了高效管理设计了一套严密的层级结构就像一个巨大的图书馆。InnoDB的存储结构从大到小依次是表空间Tablespace、段Segment、区Extent、页Page、行Row。表空间这是最大的逻辑容器你可以把它想象成整个图书馆大楼。段这是不同数据的分类区域比如“数据段”存放表数据“索引段”存放索引数据“回滚段”存放Undo Log。区这是磁盘上连续的一块空间大小固定为1MB。InnoDB在向操作系统申请空间时是以“区”为单位申请的。页这是InnoDB磁盘与内存交互的最小单位默认大小为16KB。这里有一个关键的架构师洞察为什么是16KB如果页太小比如4KB那么读取一行数据虽然快但页目录会很大管理成本高如果页太大比如64KB读取一行数据需要加载大量无关数据到内存造成浪费。16KB是MySQL社区经过长期权衡得出的“黄金尺寸”。16KB一个页不仅仅是行记录的集合它由页头Page Header简介、用户记录User Records数据、空闲空间Free Space空闲、页目录Page Directory二分查询和页尾Page Footer总结\下一个组成。------------------------------------------------------- | Page Header (页头) | -- 存放元数据页的类型、LSN、下一条记录指针等 | (36~56 Bytes) | ------------------------------------------------------- | User Records (用户记录) | -- 真正的行数据从上往下生长 | (Row 1, Row 2, ...) | ------------------------------------------------------- | Free Space (空闲空间) | -- 还没被使用的空间 ------------------------------------------------------- | Page Directory (页目录) | -- 稀疏索引指向记录用于二分查找 | (Slot 1, Slot 2, ...) | ------------------------------------------------------- | Page Footer (页尾) | -- 存放页内记录数量、下一个页ID等 | (8 Bytes) | -------------------------------------------------------关键代码视角行记录的物理存储C 语言层面行记录并不是像数组一样连续存储的而是通过Next Record Offset指针串联起来的// 源码简化版行记录的物理结构 struct rec_t { // 变长字段长度列表 // 空值列表 // 事务 ID (6字节) // 回滚指针 (7字节) // 实际列数据... // 记录头信息 (5字节) // 其中包含 next_record: 指向下一条记录的偏移量 (相对于当前记录) };为什么页目录Page Directory很重要因为行记录是链表串起来的如果要查找某条记录从头遍历是 O(N)O(N) 。页目录将记录分组每组 8~16 条目录项指向每组的最大值。查找时先对页目录二分查找 O(log⁡N)O(logN) 再在组内线性查找。这就是为什么 B 树索引在页内查找也很快的原因。行这是最小的数据单元。当你执行一条SELECT * FROM user WHERE id 1时InnoDB并不是只把id1的那一行数据从磁盘读到内存而是把这一行所在的整个页16KB全部加载进来。这就是“局部性原理”在数据库中的应用。内存结构Buffer Pool与“书桌”理论既然磁盘读写这么慢机械硬盘的随机I/O大约只有几百次/秒而内存是纳秒级InnoDB是怎么做到每秒处理上万次查询的秘密就在于Buffer Pool。你可以把Buffer Pool想象成你的书桌而磁盘是巨大的书架。如果你想看书查询数据你不会每次都跑去书架拿而是先把书拿到书桌上。如果你要写书修改数据你也不会直接在书架上涂改而是在书桌上改好等有空了再放回去。内存布局控制块与数据页在源码中Buffer Pool 被划分为两个主要部分控制块数组Control Blocks存放buf_block_t结构体包含页的元数据如 LRU 状态、脏页标志、哈希指针。数据页数组Data Blocks存放真正的 16KB 数据。对应关系Control Block[i]管理Data Block[i]。链表管理不仅仅是 LRUInnoDB 维护了三条核心链表来管理这些块Free List空闲块链表。Flush List脏页链表。注意这里只存脏页按 LSN 排序用于刷盘。LRU List存放所有已使用的页。源码视角LRU 链表的优化Midpoint Insertion标准的 LRU 有个缺陷全表扫描会把热点数据挤出去。InnoDB 将 LRU 链表分为Young 区前 5/8和Old 区后 3/8。新页插入默认插入到Old 区的头部。访问页如果页在Old 区不立即移动到头部而是更新“最后一次访问时间”。只有当时间间隔超过innodb_old_blocks_time默认 1000ms再次访问才移动到Young 区头部。如果页在Young 区直接移动到头部。// 模拟 InnoDB LRU 优化逻辑 public void accessPage(Page page) { if (page.isInOldSublist()) { if (System.currentTimeMillis() - page.lastAccessed 1000) { // 只有间隔超过 1s 再次访问才晋升到 Young 区 lruList.moveToYoungHead(page); } page.lastAccessed System.currentTimeMillis(); } else { // 在 Young 区直接移到头部 lruList.moveToHead(page); } }Buffer Pool的读流程当你查询数据时InnoDB首先会检查Buffer Pool中是否已经有了这个页。如果存在命中直接从内存返回数据速度极快。如果不存在未命中则从磁盘读取该页到Buffer Pool然后再返回数据。Buffer Pool的写流程与WAL当你更新数据时InnoDB不会立即把数据刷到磁盘因为随机写磁盘太慢了。它只是在Buffer Pool中修改内存页并将这个页标记为“脏页”。然后后台线程会在合适的时候比如Buffer Pool满了、或者系统空闲时异步地把脏页刷新到磁盘。这就引出了一个核心问题如果在你修改了内存但还没来得及刷盘的时候数据库宕机了数据岂不是丢了这就必须请出InnoDB的“救命稻草”——Redo Log。日志系统Redo Log与“草稿纸”机制Redo Log是InnoDB实现事务持久性的关键。它的原理可以用“草稿纸”来比喻。假设你要修改书上的内容但怕改错了或者还没改完灯就灭了。于是你拿出一本“草稿纸”Redo Log。在修改书之前你先把要修改的内容比如“把第10页的‘张三’改成‘李四’”写在草稿纸上。只要草稿纸写下来了哪怕书还没改灯灭了你重启后也能根据草稿纸把书改对。这就是WAL技术预写日志。先写Redo Log顺序写速度极快再写Buffer Pool内存操作速度极快最后异步刷盘随机写慢。Redo Log的物理结构Redo Log记录的是物理日志它记录的是“在某个数据页上做了什么修改”。它的大小是固定的通常配置为两个文件ib_logfile0, ib_logfile1循环写入。当写满时会覆盖旧的日志。覆盖的前提是旧的日志对应的数据页已经成功刷到了磁盘。这里涉及到一个关键指针Checkpoint。Checkpoint指向的是“已经刷盘的日志位置”。Write Pos指向的是“当前写入的位置”。当Write Pos追上Checkpoint时说明日志满了必须停止写入强制触发刷盘Checkpoint推进否则数据库会阻塞。Redo Log与Binlog的区别很多开发者容易混淆这两个日志。Redo Log是InnoDB引擎特有的是物理日志记录的是数据页的物理修改用于崩溃恢复。Binlog是MySQL Server层实现的是逻辑日志记录的是SQL语句或者行变更事件用于主从复制和数据归档。两者通过两阶段提交来保证数据一致性。崩溃恢复LSN与“时间机器” 大智慧啊InnoDB是如何做到崩溃恢复的靠的是LSN日志序列号。LSN是一个单调递增的64 位的整数不仅是日志的序号更是字节偏移量​​​​Log LSN当前 Redo Log 写到了第几个字节。Page LSN数据页最后一次被修改时的 LSN。Checkpoint LSN数据已经刷盘到的位置。每一条Redo Log都有一个LSN每一个数据页的头部也记录了它最后一次被修改时的LSN。写入流程的底层代码逻辑在源码log_write_up_to函数中写入 Redo Log 涉及复杂的锁和内存拷贝。// 伪代码Redo Log 写入流程 void log_write_up_to(lsn_t lsn) { // 1. 获取 log_write_lock mutex_enter(log_sys-write_lock); // 2. 计算写入位置 // 利用取模运算实现环形缓冲区 ulint offset lsn % log_sys-log_group_capacity; // 3. 将日志从 Log Buffer 拷贝到 Redo Log 文件 // 这是顺序写极快 os_file_write(log_file, log_buffer, offset, size); // 4. 更新 write_lsn log_sys-write_lsn lsn; mutex_exit(log_sys-write_lock); }Checkpoint 机制刷盘的“水位线”Checkpoint 是 InnoDB 崩溃恢复的核心。Fuzzy CheckpointInnoDB 不会一次性把所有脏页刷盘那样会卡死而是异步、分批地刷。Checkpoint LSN指向“所有 LSN 小于它的脏页都已经刷盘”的位置。当数据库重启时InnoDB会对比数据页的LSN和Redo Log的LSN。数据页的LSN小于Redo Log的LSN说明这个页在崩溃前被修改了但没刷盘需要用Redo Log重做。如果数据页的LSN大于Redo Log的LSN说明这个页是新的不需要恢复。细化来说读取 Redo Log 头找到 Checkpoint LSN。从 Checkpoint LSN 开始扫描 Redo Log。对于每一条日志读取对应数据页的 Page LSN。如果Page LSN Redo Log LSN说明页是旧的需要重做Redo。如果Page LSN Redo Log LSN说明页已经是新的跳过。进阶优化Change Buffer与写缓冲InnoDB加速写的机制Change Buffer。它主要用于非唯一 二级索引的更新。当你更新一个二级索引时如果这个索引页不在Buffer Pool中InnoDB不会立即从磁盘读取该页而是把更新操作缓存在Change Buffer中。// 模拟 Change Buffer 逻辑 public void updateSecondaryIndex(IndexPage page, Update update) { if (bufferPool.contains(page.getId())) { // 页在内存直接修改 page.apply(update); } else { // 页不在内存写入 Change Buffer // 这是一个磁盘操作但写的是系统表空间通常是顺序或局部顺序 changeBuffer.insert(page.getId(), update); } }等下次这个页被读取到内存时再合并更新。这样可以避免大量的随机磁盘I/O。注意唯一索引不能使用Change Buffer因为更新唯一索引时必须检查唯一性这需要读取磁盘上的旧数据无法缓冲。总结InnoDB的伟大之处在于它深刻地理解了计算机硬件的特性。它用Buffer Pool解决了内存与磁盘的速度差。它用Redo Log解决了随机写与顺序写的速度差。它用页结构解决了数据管理的粒度问题。细化通过上面的拆解我们可以看到 InnoDB 的设计哲学空间换时间用 16KB 的页结构包含 Header/Footer/Directory来管理数据虽然浪费了少量空间但换取了高效的链表管理和二分查找。用巨大的 Buffer Pool 占用内存换取磁盘 I/O 的减少。顺序写换随机写利用 Redo Log 的顺序写Log Buffer - Redo Log File来规避数据页的随机写Data Page - Disk。异步化与批处理脏页通过 Flush List 异步刷盘。Change Buffer 将多次对同一页的修改合并为一次。最后送上金句“MySQL的性能瓶颈通常在磁盘I/O。InnoDB的伟大之处在于用‘内存Buffer Pool’换‘磁盘速度’用‘顺序写Redo Log’换‘随机写’。理解了这一点你就理解了数据库性能优化的本质。”“数据库的底层优化本质上都是在与物理硬件的特性做斗争。InnoDB 通过 LRU 链表对抗内存的有限性通过 WAL 对抗磁盘随机写的低性能通过 B 树对抗数据检索的高延迟。理解了这些底层结构你就不再是在写 SQL而是在指挥硬件跳舞。”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2459695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…