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页等。
- 所有InnoDB数据存在表空间里,分两种:
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:物理日志的暴力美学
- 写入流程:
- 事务修改数据 → 先写入内存的redo log buffer。
- 事务提交 → 强制刷盘(innodb_flush_log_at_trx_commit=1)。
- 后台线程定期将脏页(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),导致页损坏。
- 解决方案:
- 脏页刷盘前,先写到Doublewrite Buffer(内存+磁盘共享区域)。
- 再将页写入真实位置,如果中途崩溃,用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躺会儿)!