当前位置: 首页 > news >正文

MySql Innodb详细解读


参考文档:https://www.cnblogs.com/acatsmiling/p/18424729

一、数据存储:从磁盘到内存的"黑帮走私"

1. 物理结构:表空间与页的江湖规矩

  • 表空间(Tablespace)
    • 所有InnoDB数据存在表空间里,分两种:
      • 系统表空间(ibdata1):存数据字典、undo log、doublewrite buffer等。
      • 独立表空间(.ibd文件):每个表单独一个文件(MySQL 5.6+默认开启)。
    • 段(Segment):表空间由段组成,比如一个B+树的叶子节点和非叶子节点各占一个段。
    • 区(Extent):每个段由连续的64个页(默认16KB/页)组成,共1MB。
    • 页(Page)InnoDB的最小操作单位(16KB),包括数据页、索引页、undo页等。

2. 页(Page)的解剖学
每个页的结构像一本固定格式的账本:

  • File Header(38字节):记录页的元数据,比如属于哪个表空间、页类型(数据页/索引页)、上一页下一页指针(B+树叶子链表)。
  • Page Header(56字节):页内统计信息,比如槽位数量、空闲空间起始位置。
  • Infimum + Supremum(26字节):两个虚拟的行记录,标识页的最小和最大边界。
  • User Records(行记录):实际存储的数据行,按主键顺序排列(聚簇索引)。
  • Free Space:未使用的空间。
  • Page Directory(槽位):存储行记录的相对位置(类似目录),用于快速二分查找。
  • File Trailer(8字节):校验页完整性(比如是否写完整)。

吐槽

  • “页就像一本16KB的账本,写满了就换一本(分裂),查数据得先翻目录(Page Directory)!”
  • “File Trailer就是页的‘封条’,发现破损直接报警(崩溃恢复)!”

二、B+树:InnoDB的"骨骼系统"

1. B+树的实现细节

  • 非叶子节点(索引页):只存键值+子页指针,比如(key, child_page_number)
  • 叶子节点(数据页):存完整数据行(聚簇索引)或主键+索引列(二级索引)。
  • 页分裂与合并
    • 分裂:插入数据导致页超过16KB → 分裂成两个页,父节点更新指针。
    • 合并:删除数据后页利用率过低 → 合并相邻页(但InnoDB懒合并,减少开销)。

2. 自增主键的玄学

  • 顺序写入:自增主键让新数据按顺序插入B+树最右端,减少页分裂。
  • 随机主键(如UUID)的灾难:数据随机插入 → 页频繁分裂 → 性能暴跌,磁盘碎片爆炸。

底层真相

  • B+树的高度决定查询性能:3层B+树可存约(16KB / (主键大小 + 指针大小))^3条数据。比如主键是BIGINT(8字节)+指针6字节 → 每页存约16KB / 14B ≈ 1170个指针 → 3层树存1170^3 ≈ 16亿条数据。
  • 页内二分查找:Page Directory槽位存储行记录的相对偏移量,查找时先二分槽位,再定位行。

吐槽

  • “B+树的高度就是数据库的‘血压’,超过3层赶紧分库分表!”
  • “自增主键是B+树的亲爹,UUID是后妈养的!”

三、事务与锁:并发控制的"黑暗森林"

1. 事务的ACID实现

  • 原子性(A):靠undo log → 事务失败时反向执行undo log回滚。
  • 一致性(C):靠约束(主键、外键)锁机制 → 防止中间状态被其他事务看到。
  • 隔离性(I):靠MVCC(多版本并发控制)和 → 不同隔离级别用不同策略。
  • 持久性(D):靠redo log → 事务提交前先写redo log,保证崩溃可恢复。

2. 锁机制的底层实现

  • 行级锁:本质是锁索引(没有索引就锁全表!)。
  • 锁的内存结构
    • 锁对象(Lock Struct):记录事务ID、锁类型(共享/排他)、锁模式(记录锁、间隙锁、临键锁)。
    • 哈希表管理:用(space_id, page_no, heap_no)哈希定位锁(heap_no是页内行号)。
  • 间隙锁(Gap Lock):锁住索引区间,防止幻读。比如WHERE id > 100,锁住(100, +∞)的空隙。

3. MVCC的黑暗魔法

  • 版本链:每条数据行隐藏两个字段:
    • DB_TRX_ID:最近修改该行的事务ID。
    • DB_ROLL_PTR:指向undo log的回滚指针,构成版本链。
  • ReadView:事务启动时生成,包含:
    • m_ids:当前活跃事务ID列表。
    • min_trx_id:最小活跃事务ID。
    • max_trx_id:下一个待分配事务ID。
    • creator_trx_id:创建该ReadView的事务ID。
  • 可见性判断
    根据ReadView和行的DB_TRX_ID,判断该行是否对当前事务可见(类似时间戳快照)。

底层真相

  • MVCC的读操作不加锁:通过版本链和ReadView实现“读旧数据”,避免锁竞争。
  • 写操作依然加锁:保证数据一致性,其他事务的写操作被阻塞。

吐槽

  • “MVCC就是数据库的‘平行宇宙’,每个事务看到自己的世界线!”
  • “没有索引还敢更新数据?锁全表警告!”

四、日志系统:崩溃恢复的"时光机"

1. Redo Log:物理日志的暴力美学

  • 写入流程
    1. 事务修改数据 → 先写入内存的redo log buffer
    2. 事务提交 → 强制刷盘(innodb_flush_log_at_trx_commit=1)。
    3. 后台线程定期将脏页(Buffer Pool中修改过的页)刷到磁盘。
  • LSN(Log Sequence Number)
    • 全局递增的日志序列号,标识redo log的写入位置。
    • 数据页头记录Page LSN,崩溃恢复时对比LSN,决定是否应用redo log。
  • Checkpoint机制
    • 定期将Buffer Pool的脏页刷盘,并记录Checkpoint LSN
    • 崩溃恢复时,只需从Checkpoint LSN开始重放redo log。

2. Undo Log:逻辑日志的时光倒流

  • 写入位置:存在系统表空间(ibdata1)或独立undo表空间(MySQL 8.0+)。
  • 版本链管理:每次修改前,将旧数据写入undo log,并通过DB_ROLL_PTR链接成链。
  • Purge线程:后台清理无用的undo log(无活跃事务引用时)。

3. Binlog:逻辑日志的主从复制

  • 两阶段提交(2PC)
    • Prepare阶段 → 写redo log(标记为prepare)。
    • Commit阶段 → 写binlog → 写redo log(标记为commit)。
  • 崩溃恢复逻辑
    • 如果redo log是prepare状态 → 检查对应的binlog是否完整。
    • 若binlog完整 → 提交事务;若binlog不完整 → 回滚事务。

底层真相

  • Redo Log是InnoDB的私有日志,物理记录数据页的修改。
  • Binlog是MySQL Server层的日志,逻辑记录SQL语句或行变更。
  • 两阶段提交是两者的‘结婚协议’,保证数据一致性。

吐槽

  • “Redo Log是InnoDB的‘遗嘱’,Binlog是MySQL的‘广播稿’!”
  • “两阶段提交就像结婚领证,先宣誓(prepare),再发朋友圈(commit)!”

五、内存管理:Buffer Pool的"权力游戏"

1. Buffer Pool的核心地位

  • 内存缓存池:缓存数据页和索引页,减少磁盘IO。
  • LRU算法优化
    • 传统LRU链表分为Young(热数据)和Old(冷数据)区域,防止全表扫描污染缓存。
    • 新加载的页先插入Old区,只有被二次访问才晋升到Young区。

2. Change Buffer:写缓冲的偷懒艺术

  • 适用场景:非唯一二级索引的插入/更新操作。
  • 原理
    • 若目标页不在Buffer Pool → 将修改记录到Change Buffer,等下次读取该页时再合并修改。
    • 减少随机IO,提升写性能(尤其适合写多读少的场景)。

3. Doublewrite Buffer:防数据页写断裂

  • 问题背景:页写入磁盘时可能部分写入(比如16KB只写了8KB),导致页损坏。
  • 解决方案
    1. 脏页刷盘前,先写到Doublewrite Buffer(内存+磁盘共享区域)。
    2. 再将页写入真实位置,如果中途崩溃,用Doublewrite Buffer恢复。

底层真相

  • Change Buffer是‘延迟写’的骚操作,但唯一索引无法使用(需要立即检查唯一性)。
  • Doublewrite Buffer是InnoDB的‘备胎’,牺牲10%写性能换数据页安全。

吐槽

  • “Change Buffer就是快递代收点,攒一波再送!”
  • “Doublewrite Buffer:你可以说我慢,但不能说我菜(数据损坏)!”

六、崩溃恢复:InnoDB的"复活甲"

1. 恢复流程

  • Step 1:重放Redo Log
    从Checkpoint LSN开始,按顺序应用所有redo log,将数据页恢复到崩溃前的状态。
  • Step 2:回滚未提交事务
    扫描undo log,对未提交的事务执行回滚操作。
  • Step 3:清理残留锁
    崩溃时可能遗留未释放的锁,全部强制释放。

2. 核心依赖

  • Redo Log的持久化:保证已提交事务的数据不丢失。
  • Undo Log的完整性:保证未提交事务能正确回滚。

底层真相

  • 崩溃恢复就像玩单机游戏的‘读档’,Redo Log是存档文件,Undo Log是回档指令。

七、总结:InnoDB底层原理的"七宗罪"

模块核心机制优化启示
数据存储页分裂、区段管理、B+树高度用自增主键,避免随机写入
事务与锁MVCC版本链、间隙锁、两阶段提交短事务、避免长锁、合理选择隔离级别
日志系统Redo持久化、Undo回滚、Binlog复制平衡innodb_flush_log_at_trx_commit参数
内存管理Buffer Pool LRU、Change Buffer偷懒分配足够Buffer Pool,监控命中率
崩溃恢复Redo重放、Undo回滚、Doublewrite备胎确保日志文件足够大,用高可靠存储

终极暴论

  • “不懂InnoDB底层原理的调优,就像蒙眼飙车——迟早翻车!”
  • “B+树、MVCC、Redo Log——这三板斧砍不明白,别自称数据库高手!”
  • “记住:InnoDB的每一行代码,都是性能和可靠性的血腥博弈!”

完活儿!现在你可以去给MySQL内核贡献代码了(或者去ICU躺会儿)!

http://www.xdnf.cn/news/32851.html

相关文章:

  • 【预告】【k8s系列6】RKE搭建Kubernetes集群
  • Codeforces Educational Round 177 Div. 2 【B题,C待补
  • Elasticsearch:使用 ES|QL 进行搜索和过滤
  • 嵌入式linux系统中内存管理的方法与实现
  • EKF公式推导
  • 工业级向量检索核心技术:IVF-PQ原理与全流程解析
  • [Android] 豆包爱学v4.5.0小学到研究生 题目Ai解析
  • 【IaaS自动化运维】Ansible与Terraform集成
  • 【仓颉 + 鸿蒙 + AI Agent】CangjieMagic框架(16):ReactExecutor
  • 【重学Android】02.Java环境配置的一些分享
  • 高精度算法(加、减、乘、除、阶乘和)​
  • QML Rectangle 组件
  • 直线轴承常规分类知多少?
  • 洛谷P1177【模板】排序:十种排序算法全解(1)
  • Keil A51汇编伪指令
  • 【机器学习】朴素贝叶斯算法:原理剖析与实战应用
  • Java学习手册:Web 应用架构概述
  • 卷积神经网络(CNN)详解
  • 【嵌入式】——Linux系统远程操作和程序编译
  • 前端面试真题集合(一)
  • Python语法系列博客 · 第9期[特殊字符] 函数参数进阶:*args、**kwargs 与参数解包技巧
  • 树莓派5-开发应用笔记
  • Java Web 之 Tomcat 100问
  • git合并分支并推送
  • 为什么浮点数会搞出Infinity和NAN两种类型?浮点数的底层原理?IEEE 754标准揭秘?
  • matlab 环形单层柱状图
  • 解锁异步JavaScript性能:从事件循环(Event Loop)到Promise与Async/Await的最佳实践
  • 电商平台计算订单成交额是不是要去除退款退货的
  • CMFA在自动驾驶中的应用案例
  • 多线程使用——线程安全、线程同步