MySQL 体系结构与存储引擎
0. 引言
MySQL 是互联网时代最普及的开源关系型数据库。理解 MySQL 的体系结构,是掌握索引、事务、锁、高可用等一切后续主题的前提。本文以 MySQL 8.0(当前主流生产版本,8.0 与 8.4 LTS)为基准,解析 MySQL 的层级架构、一条 SQL 的完整执行链路,以及 InnoDB 存储引擎的内存/线程/物理结构,并对齐 5.7 → 8.0 的关键演进。
1. 总体架构:Server 层与存储引擎层
MySQL 的架构精髓是插件式存储引擎:Server 层(连接管理、解析、优化、缓存)与存储引擎层解耦,任何实现了统一接口的引擎都可以接入 MySQL。
| 层级 | 核心模块 | 职责 |
|---|---|---|
| Server 层 | Connection Pool | 管理连接线程、用户认证与权限校验 |
| Server 层 | Parser | SQL 语法解析,生成解析树(语法错误在此暴露) |
| Server 层 | Optimizer | 生成执行计划、选择索引、决定连接顺序 |
| Server 层 | 执行引擎 | 按执行计划调用存储引擎 API |
| 引擎层 | InnoDB | 事务、行锁、MVCC、崩溃恢复(默认引擎) |
| 引擎层 | MyISAM | 无事务、表锁、只读场景的遗留选择 |
| 物理层 | 文件系统 | 表空间文件、redo/undo 日志、binlog、慢查询日志 |
8.0 一个重要变化:查询缓存(Query Cache)被彻底移除(8.0.3 起废弃)。5.7 时代"相同 SQL 直接返回缓存结果"的优化在写多读少、缓存失效频繁的场景下弊大于利,官方在 8.0 直接删除该组件。
2. 一条 SQL 的完整执行链路
以 SELECT 为例,SQL 的生命周期如下:
各步骤要点:
- 连接:客户端通过 TCP 或 Unix Socket 建立连接,
Connection Pool为每个连接分配线程;认证失败在此阶段被拒绝(8.0 默认caching_sha2_password认证插件)。 - 解析:Parser 将 SQL 拆分为 Token,构建解析树。此阶段只检查语法,不检查表是否存在。
- 预处理:语义检查——表/列是否存在、权限是否足够、
*展开。 - 优化:Optimizer 基于统计信息(
mysql.innodb_table_stats等)选择访问路径。这是 DBA 通过EXPLAIN能干预的关键环节。 - 执行:执行引擎通过存储引擎 API 逐行读取数据(InnoDB 的读取单位是 16KB 数据页)。
- 返回:Server 层完成
ORDER BY/GROUP BY/LIMIT等操作后返回结果集。
DML 与 SELECT 的差异:
UPDATE/DELETE在执行阶段会先定位记录,再在事务上下文中修改并生成 redo/undo 日志,最后在提交时刷盘——比 SELECT 多出事务与日志两条链路(详见《深入理解事务与锁机制》)。
3. 存储引擎全景
MySQL 支持多种存储引擎,通过 SHOW ENGINES 查看:
| 引擎 | 事务 | 锁粒度 | 特点 | 现状 |
|---|---|---|---|---|
| InnoDB | ✅ ACID | 行锁 | MVCC、聚簇索引、崩溃恢复 | 默认引擎(5.5.5+),绝对主流 |
| MyISAM | ❌ | 表锁 | 压缩、全文索引(旧版) | 遗留;8.0 中仍可用但不建议 |
| Memory | ❌ | 表锁 | 数据全内存,重启丢失 | 临时表/缓存场景;8.0 起临时表改由 InnoDB/临时引擎承担 |
| Archive | ❌ | 行锁 | 高压缩、只支持 INSERT/SELECT | 日志归档场景 |
| CSV | ❌ | 表锁 | 数据即 CSV 文件 | 数据交换场景 |
8.0 中 MyISAM 系统表全部被 InnoDB 取代(数据字典统一存储),且官方明确 InnoDB 是唯一推荐的全功能引擎。MySQL 8.0 的临时表默认使用 InnoDB 或专用临时引擎(TempTable),不再依赖 Memory 引擎。
4. InnoDB 架构:实例层与物理层
4.1 总体结构
4.2 内存结构
Buffer Pool(缓冲池)——InnoDB 性能的心脏:
- 缓存数据页、索引页、undo 页、自适应哈希索引(AHI)、锁信息等;
- 采用 LRU 改进算法(分 old/new 两段:新数据先入 old 段,第二次访问才晋升 new 段),防止全表扫描把热点数据挤出去;
- 8.0 支持动态调整:
innodb_buffer_pool_size可在运行时修改(5.7+),且支持多实例(innodb_buffer_pool_instances,8.0 默认按 128MB 分片); - 配置经验:单机单实例时设为物理内存的 60%~80%;通过
SHOW GLOBAL STATUS LIKE '%buffer_pool%'观察命中率与等待时间。
Redo Log Buffer:暂存数据修改产生的 redo 日志,默认 16MB(innodb_log_buffer_size),事务提交时按 innodb_flush_log_at_trx_commit 策略刷盘(0/1/2)。
Double Write Buffer(双写缓冲):解决"页撕裂"——数据页写一半时断电,产生半页损坏。双写先把整页副本写入双写区域再落盘,崩溃恢复时可用副本修复。8.0.20+ 支持独立双写文件(innodb_doublewrite_dir),并可在不支持的操作系统关闭(innodb_doublewrite=OFF)。
4.3 后台线程
| 线程 | 职责 | 演进 |
|---|---|---|
| Master Thread | 主循环调度:1s/10s 周期任务(刷日志、刷脏页、合并 insert buffer、purge) | 5.6 前"全能",5.6+ 逐步拆分 |
| Page Cleaner Thread | 专职脏页刷新(可配置多个) | 5.6 引入,解决主线程刷脏阻塞 |
| Purge Thread | 回收已提交事务的 undo 页 | 5.6 引入,可多实例 |
| Read/Write Thread | 并行读写数据文件(innodb_read_io_threads/innodb_write_io_threads) | 提升 IO 吞吐 |
| Redo Log Thread | 刷新 redo 日志缓冲到磁盘 | 8.0.30+ 独立线程 |
4.4 物理结构
8.0 的物理文件布局相比 5.7 有重大调整:
| 文件 | 5.7 | 8.0 |
|---|---|---|
| 数据字典 | ibdata1 + 各表 .frm | 统一存储在 InnoDB 数据字典(原子 DDL:DDL 要么成功要么回滚,不再有 .frm 与 .ibd 不一致问题) |
| Undo 日志 | ibdata1 + 独立 undo 表空间 | 完全独立:innodb_undo_tablespaces、innodb_undo_directory、innodb_max_undo_log_size、innodb_undo_log_truncate(默认开启,支持运行时截断回收) |
| Redo 日志 | ib_logfile0/1(固定大小) | #innodb_redo 目录 + innodb_redo_log_capacity(8.0.30+ 容量管理,默认 100MB,动态扩展) |
| 表空间 | 独立表空间默认开启 | 支持 CREATE TABLESPACE 通用表空间、CREATE UNDO TABLESPACE ADD DATAFILE 'xxx.ibu' 动态创建 |
redo 日志容量管理(8.0.30+):innodb_redo_log_capacity 取代了 innodb_log_file_size 的固定文件配置,redo 文件在 #innodb_redo 目录中按需增减。可通过 performance_schema.innodb_redo_log_files 查询每个文件的 LSN 区间与使用率:
SELECT FILE_ID, START_LSN, END_LSN, SIZE_IN_BYTES, IS_FULL
FROM performance_schema.innodb_redo_log_files;检查点(checkpoint):redo 是循环复用的。当 checkpoint_age(检查点 LSN 与最新 LSN 之差)逼近容量上限时,InnoDB 强制刷新脏页推进检查点——redo 太小会导致频繁强制 checkpoint,拖慢写入;设置过大则崩溃恢复时间变长。生产建议监控 Innodb_redo_log_capacity 相关指标,写入密集场景通常需要 2-8GB。
5. ARIES 三原则:InnoDB 的恢复基石
InnoDB 的崩溃恢复基于经典的 ARIES 算法,核心三原则:
- WAL(Write Ahead Logging):先写日志、后写数据。事务提交时 redo 落盘成功即不丢失(配合刷新策略),后续由 checkpoint 保证磁盘数据与日志一致;
- Redo 记录变更后的值:崩溃后用 redo 前滚(redo)所有已提交但未落盘的修改;
- Undo 记录变更前的值:崩溃后用 undo 回滚(undo)所有未提交事务的修改,同时支撑 MVCC 多版本读。
这也是"为什么 MySQL 断电不丢已提交数据"的根本原因:提交的语义 = redo 已落盘,而不是数据页已落盘。
6. InnoDB vs MyISAM:功能与性能对比
| 维度 | InnoDB | MyISAM |
|---|---|---|
| 事务(ACID) | ✅ | ❌ |
| 锁粒度 | 行锁 + 间隙锁 | 仅表锁 |
| 隔离级别 | 4 种,默认 RR(可重复读) | 无 |
| MVCC | ✅ | ❌ |
| 崩溃恢复 | ✅(redo + undo) | ❌(需手动修复) |
| 外键 | ✅ | ❌ |
| 聚簇索引 | ✅(数据按主键组织) | ❌(堆表 + 索引文件分离) |
| 全文索引 | ✅(8.0 支持中文分词) | ✅ |
| 压缩 | ✅(页压缩/透明页压缩) | ✅(表压缩) |
| 适用场景 | 通用 OLTP,绝对首选 | 只读/归档类历史遗留 |
性能差异:InnoDB 在读写混合场景随 CPU 核数近线性扩展(官方测试可达近 9000 TPS),而 MyISAM 因读写互斥、表锁串行,性能与核数无关(<500 TPS)。新表一律使用 InnoDB,MyISAM 只存在于历史系统中。
7. 小结
- MySQL = Server 层(连接/解析/优化/执行)+ 插件式存储引擎层,8.0 移除查询缓存、数据字典统一入 InnoDB;
- SQL 执行链路:连接→解析→预处理→优化→执行→返回,DBA 的干预点主要在优化器(索引与执行计划);
- InnoDB 架构:Buffer Pool(改进 LRU)+ 双写 + 多线程刷盘,物理层 8.0 全面重构(原子 DDL、独立 undo、动态 redo 容量);
- ARIES 三原则(WAL + redo 前滚 + undo 回滚)是崩溃恢复的理论根基;
- 引擎选型:InnoDB 一统天下,MyISAM 只读遗留。
下一章深入事务与锁机制:隔离级别、MVCC、行锁/间隙锁/临键锁与死锁。