MySQL 核心架构与底层原理总结
1. MySQL 体系架构与核心机制
在深入研究 MySQL 的日志和内存机制之前,我们需要先理解 MySQL 的分层架构以及它如何保证数据库最核心的特性——ACID。
1.1 MySQL 逻辑架构
MySQL 的架构可以清晰地分为两层:Server 层 和 存储引擎层。这种分层设计决定了不同日志(Binlog vs Redo Log)所处的位置和作用范围。
-
Server 层(通用层) :
- 包含连接器、查询缓存(8.0已移除)、分析器、优化器、执行器等。
- 核心职责:负责 SQL 语法解析、优化、以及与客户端的交互。
- 对应日志:Binlog (归档日志) 就在这一层产生。因此 Binlog 是所有存储引擎共有的,不涉及底层数据页的物理细节。
-
存储引擎层(插件式) :
- 负责数据的存储和提取。最常用的是 InnoDB。
- 核心职责:负责事务管理、锁机制、以及数据的持久化。
- 对应日志:Redo Log (重做日志) 和 Undo Log (回滚日志) 是 InnoDB 引擎特有的,它们深入到物理“数据页”的级别。
1.2 ACID 特性与实现的对应关系
我们学习所有的日志(Redo/Undo)和机制(MVCC/锁),最终目的都是为了实现事务的 ACID 特性。面试时,将组件与 ACID 对应起来是满分回答:
-
A (Atomicity) 原子性:要么全做,要么全不做。
- 实现依靠:Undo Log。如果事务失败,靠 Undo Log 回滚。
-
C (Consistency) 一致性:数据库从一个一致性状态变到另一个一致性状态。
- 实现依靠:这是最终目的,依靠 A、I、D 三者共同保证,以及代码层面的约束。
-
I (Isolation) 隔离性:多个事务并发执行不互相干扰。
- 实现依靠:锁 (Locks) 和 MVCC (多版本并发控制) 。
-
D (Durability) 持久性:事务一旦提交,数据永不丢失。
- 实现依靠:Redo Log 和 Double Write Buffer (双写缓冲) 。
1.3 核心设计思想:WAL (Write-Ahead Logging)
MySQL 能够支持高性能写入的关键技术是 WAL(预写日志) 。
-
问题:如果每次修改数据都直接去写硬盘上的数据文件,由于是随机 IO,速度会极慢。
-
解决:先写日志(Redo Log),再更新内存(Buffer Pool)。
-
机制:
- 当有记录需要更新时,InnoDB 会先把记录写到 Redo Log(这是顺序 IO,速度快)。
- 同时更新内存(Buffer Pool)。
- 此时事务就算完成了。
- 至于什么时候真正把数据写到磁盘的数据文件里,由后台线程慢慢做(这个过程叫 Checkpoint)。
1.4 数据存储结构与索引组织 (B+ Tree)
有了架构和日志的概念后,我们需要知道数据到底是怎么存的。在 InnoDB 中,数据即索引,索引即数据。
-
页 (Page) —— 最小的物理单位:InnoDB 不会按“行”读取数据,那样太慢。它和磁盘交互的最小单位是 Page(默认 16KB)。
-
索引结构 (B+ Tree) :
- 高度低:B+ 树非叶子节点只存索引(key),不存数据,能容纳更多索引项。一张千万级的表,树高通常只有 3 层,意味着只需 3 次磁盘 IO。
- 范围查询强:B+ 树叶子节点之间有双向指针连接,非常适合
> 100这种范围扫描。
-
聚簇索引 (Clustered Index) :
- 即主键索引。特点:叶子节点存的是完整的整行数据。InnoDB 的数据文件(.ibd)本身就是一棵聚簇索引树。
-
非聚簇索引 (Secondary Index) :
- 即普通索引。特点:叶子节点存的是 索引列的值 + 主键 ID。查询时如果需要非索引列,需要拿着 ID 回到聚簇索引再查一遍,称为回表。
2. MySQL 的主要日志
Binlog 主要用来对数据库进行备份、恢复和复制。Redo Log 和 Undo Log 主要用于事务,记录的是数据修改操作和回滚操作。
2.1 Binlog (归档日志)
Binlog 是 MySQL Server 层记录的所有 DDL 和 DML 语句的二进制日志。
-
核心作用:主从复制 (Replication) 和 数据恢复 (Point-in-Time Recovery) 。
-
物理结构:追加写(Append-only),逻辑日志。
-
格式:
-
STATEMENT:记 SQL 语句(可能导致主从不一致)。 -
ROW:记每一行的具体变更(最安全,但空间占用大)。 -
MIXED:混合模式。
-
-
刷盘策略:由
sync_binlog控制。-
0:交给 OS,高性能但风险大。 -
1:每次提交都 fsync,最安全。
-
2.2 Redo Log (重做日志)
Redo Log 是 InnoDB 用于实现崩溃恢复(Crash Safe)的机制。
-
核心作用:保证事务的 持久性 (Durability) 。
-
物理结构:物理日志(记录“某页某偏移量修改了什么”),循环写入(Ring Buffer)。
-
刷盘策略:由
innodb_flush_log_at_trx_commit控制。-
1(默认):每次提交都写入磁盘,最安全。
-
2.3 Undo Log (回滚日志)
Undo Log 用于事务回滚和 MVCC。
- 核心作用:保证事务的 原子性 (Atomicity) 和 隔离性 (MVCC) 。
- 物理结构:逻辑日志(记录相反操作)。
- 存储位置:Undo Log 本身被当做“数据”对待,存放在 Buffer Pool 中,也会生成对应的 Redo Log。
2.4 三大日志总结对比
| 特性 | Redo Log (重做日志) | Binlog (归档日志) | Undo Log (回滚日志) | ||||
|---|---|---|---|---|---|---|---|
| 层级 | InnoDB 存储引擎层 | MySQL Server 层 | InnoDB 存储引擎层 | ||||
| 内容 | 物理日志 (Page 的修改) | 逻辑日志 (SQL/行变化) | 逻辑日志 (反向操作) | ||||
| 写入方式 | 循环写 (环形,覆盖旧值) | 追加写 (不覆盖) | 随机读写 (复用空间) | ||||
| 缓冲区 | innodb_log_buffer | binlog_cache(Per Thread) | Buffer Pool(作为数据页) | ||||
| 刷盘参数 | innodb_flush_log_at_trx_commit | sync_binlog | 随 Checkpoint 机制 | ||||
| 关键风险 | Write Pos 追上 Checkpoint (抖动) | 没刷盘导致主从不一致 | 长事务导致空间膨胀 | ||||
| 主要用途 | 崩溃恢复 (Crash Safe) | 主从复制、数据恢复 | 事务回滚、MVCC |
2.5 Redo Log 与 Undo Log 比较
- 目的:Redo Log 负责“前滚”(恢复数据),Undo Log 负责“回滚”(撤销操作)。
- 记录内容:Redo Log 记物理修改,Undo Log 记逻辑反向操作。
2.6 MVCC (多版本并发控制) 详解
MVCC 是实现 RC (读已提交) 和 RR (可重复读) 隔离级别的核心技术。核心思想是:读不加锁,读写不冲突。
-
隐藏字段:每行数据都有
trx_id (最近修改事务ID) 和roll_pointer(回滚指针)。 -
Undo Log 版本链:旧数据通过指针串联,形成历史版本链。
-
Read View (一致性视图) :快照读时生成的“裁判”。包含
m_ids (活跃事务列表)、min_trx_id、max_trx_id。- 可见性规则:通过比较数据的
trx_id和 Read View 中的 ID,判断当前事务能看到哪个版本的数据。 - RC vs RR:RC 每次 Select 都生成新的 Read View;RR 只在第一次 Select 生成,之后复用。
- 可见性规则:通过比较数据的
3. 两阶段提交 (Two-Phase Commit)
MySQL 使用两阶段提交来保证 Redo Log 和 Binlog 的逻辑一致性。
3.1 流程演示 (Update 语句)
假设执行 UPDATE t_user SET age = 18 WHERE id = 1; (双1模式):
-
执行阶段:
- 写入 Undo Log (内存)。
- 更新 Buffer Pool 中的数据页 (内存变脏)。
- 写入 Redo Log Buffer (内存)。
-
提交阶段 (2PC) :
- Prepare 阶段:Redo Log 落盘 (fsync),标记为
PREPARE。 - Binlog 阶段:Binlog 落盘 (fsync)。
- Commit 阶段:Redo Log 标记为
COMMIT(通常只需内存操作,依赖 Binlog 存在性)。
- Prepare 阶段:Redo Log 落盘 (fsync),标记为
3.2 崩溃恢复逻辑
- 如果 Redo Prepare + Binlog 缺失 -> 回滚。
- 如果 Redo Prepare + Binlog 完整 -> 提交。
4. InnoDB Buffer Pool (内存架构)
InnoDB 的核心内存组件,用于缓解 CPU 与磁盘的速度差异。
4.1 核心作用
- 读缓存:命中内存则不读盘。
- 写缓存:修改只改内存(脏页),通过 Checkpoint 异步刷盘。
4.2 内存管理 (三大链表)
- Free List:空闲页链表。
- Flush List:脏页链表(按修改时间排序,用于 Checkpoint)。
- LRU List:管理冷热数据淘汰。
4.3 改进版 LRU 算法
为了防止 全表扫描污染 Buffer Pool,InnoDB 将 LRU 分为 Young 区 (热, 63%) 和 Old 区 (冷, 37%) 。
- 新读入的页先放 Old 区头部。
- 只有在 Old 区停留超过 1 秒再次被访问,才移入 Young 区。
4.4 配置建议
-
innodb_buffer_pool_size:建议设为物理内存的 70%-80% (专用服务器)。 -
innodb_buffer_pool_instances:内存 > 1G 时建议调大,减少锁竞争。 - 预热:开启
dump_at_shutdown 和load_at_startup。
5. Checkpoint 与 持久化机制
协调内存脏页与磁盘文件的同步,释放 Redo Log 空间。
5.1 核心概念:LSN
- Redo LSN (写到哪) vs Checkpoint LSN (刷到哪)。
- 差值即为“未落盘的脏数据量”。
5.2 触发场景
- Master Thread:定时异步刷。
- Redo Log 空间不足 (Async/Sync Flush) :最危险的情况,会导致性能抖动。
- 内存不足:Free List 空了,被迫刷脏页腾地。
5.3 Double Write Buffer (双写缓冲) —— 最后一道防线
Q: 有了 Redo Log 为什么还要 Double Write?
A: 解决 页断裂 (Partial Page Write) 问题。
-
问题:如果 16KB 的页只写了 4KB 就断电,物理页损坏,Redo Log 无法恢复。
-
流程:
- 脏页先 memcpy 到内存的双写缓冲区。
- 顺序写 入系统表空间 (ibdata1) 的双写区域 (备份)。
- 离散写 入真正的数据文件 (.ibd)。
-
恢复:启动时若发现页损坏,先从 ibdata1 里的备份还原,再重放 Redo Log。