MySQL 架构与存储引擎
问题
MySQL 的整体架构是怎样的?InnoDB 和 MyISAM 有什么区别?一条 SQL 在 MySQL 中是怎么执行的?
答案
一、MySQL 分层架构
MySQL 采用 Server 层 + 存储引擎层 的分层设计,这是理解所有 MySQL 机制的基础:
Server 层
| 组件 | 职责 |
|---|---|
| 连接器 | 管理客户端连接、身份认证、权限查询 |
| 分析器 | 词法分析(识别关键字)→ 语法分析(构建语法树) |
| 优化器 | 生成执行计划、选择索引、JOIN 顺序优化 |
| 执行器 | 校验权限 → 调用存储引擎接口逐行/批量获取数据 |
存储引擎层
存储引擎是 插件式 的,通过统一的 Handler API 与 Server 层交互。每张表可以使用不同的引擎。
二、一条 SQL 的执行全流程
以 SELECT * FROM users WHERE id = 1 为例:
对于 UPDATE 语句,额外涉及:
- Buffer Pool 中修改数据页(脏页)
- 写 redo log(WAL 机制,保证崩溃恢复)
- 写 binlog(Server 层,用于主从复制)
- 两阶段提交(保证 redo log 和 binlog 一致性)
三、InnoDB 引擎详解
InnoDB 是 MySQL 5.5+ 的 默认存储引擎,也是面试几乎所有 MySQL 问题的核心。
InnoDB 内存架构
Buffer Pool 工作原理
Buffer Pool 是 InnoDB 最核心的内存结构,默认 128MB(生产环境通常设为物理内存的 60~80%)。
改进的 LRU 算法:
传统 LRU 的问题:全表扫描会将大量冷数据加载到 Buffer Pool,把热数据挤出去。
InnoDB 的解决方案 — 分区 LRU:
- young 区(热数据,5/8):频繁访问的页
- old 区(冷数据,3/8):新加载的页先放 old 区
- 只有在 old 区停留超过
innodb_old_blocks_time(默认 1000ms)后再次被访问,才晋升到 young 区
这样全表扫描的页会在 old 区快速淘汰,不影响热数据。
四、InnoDB vs MyISAM
| 特性 | InnoDB | MyISAM |
|---|---|---|
| 事务 | ✅ 支持 | ❌ 不支持 |
| 行级锁 | ✅ 支持 | ❌ 仅表锁 |
| 外键 | ✅ 支持 | ❌ 不支持 |
| MVCC | ✅ 支持 | ❌ 不支持 |
| 崩溃恢复 | ✅ redo log | ❌ 无 |
| 聚簇索引 | ✅ 数据与主键索引一起 | ❌ 索引与数据分离 |
| 全文索引 | ✅(5.6+) | ✅ |
| COUNT(*) | 需遍历(MVCC 不同事务看到不同数据) | 存储了行数,直接返回 |
| 存储文件 | .ibd(表空间) | .MYD(数据)+ .MYI(索引) |
面试要点
为什么 InnoDB 的 COUNT(*) 比 MyISAM 慢?
因为 InnoDB 支持 MVCC,不同事务在同一时刻看到的行数可能不同,所以无法像 MyISAM 那样简单存储一个总行数,必须逐行判断可见性。
五、其他存储引擎
| 引擎 | 特点 | 适用场景 |
|---|---|---|
| Memory | 数据全在内存,速度快,重启丢失 | 临时表、缓存 |
| Archive | 只支持 INSERT/SELECT,高压缩 | 日志归档 |
| CSV | 数据以 CSV 文件存储 | 数据交换 |
| NDB | MySQL Cluster 引擎 | 分布式集群 |
常见面试问题
Q1: MySQL 查询缓存为什么被废弃了?
答案:
MySQL 查询缓存在 8.0 被完全移除,原因:
- 命中率极低:任何对表的写操作都会使该表所有缓存失效
- 并发瓶颈:查询缓存使用全局互斥锁,高并发时成为瓶颈
- 内存浪费:缓存管理本身消耗内存
替代方案:在应用层使用 Redis 等外部缓存。
Q2: 为什么 InnoDB 使用 B+ 树而不是 B 树?
答案:
| 特性 | B 树 | B+ 树 |
|---|---|---|
| 数据存储 | 所有节点都存数据 | 只有叶子节点存数据 |
| 范围查询 | 需要中序遍历 | 叶子节点有链表,直接遍历 |
| 磁盘 IO | 非叶子节点大(含数据),每页存的 key 少 | 非叶子节点小(只存 key),每页存的 key 多,树更矮 |
| 查询稳定性 | 不稳定(可能在非叶子命中) | 稳定(必须到叶子节点) |
核心原因:B+ 树更矮(3~4 层即可存千万级数据),IO 次数更少,且叶子节点链表天然支持范围查询。
Q3: 一条 UPDATE 语句的执行流程?
答案:
1. 连接器 → 分析器 → 优化器 → 执行器(同 SELECT)
2. 执行器调用 InnoDB 接口读取目标行
3. InnoDB 在 Buffer Pool 中查找(未命中则从磁盘加载)
4. 记录旧值到 undo log(用于回滚和 MVCC)
5. 在 Buffer Pool 中修改数据页(脏页)
6. 写 redo log(prepare 状态) ← WAL
7. 写 binlog
8. 提交事务:redo log 改为 commit 状态 ← 两阶段提交
Q4: Buffer Pool 的大小怎么设置?
答案:
-- 查看当前 Buffer Pool 大小
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
-- 推荐设置为物理内存的 60~80%
SET GLOBAL innodb_buffer_pool_size = 8589934592; -- 8GB
- 通过
SHOW ENGINE INNODB STATUS查看 Buffer Pool 命中率 - 命中率应 > 99%,低于 95% 说明需要增大
Q5: InnoDB 的四大特性是什么?
答案:
- 插入缓冲(Change Buffer):对非唯一二级索引的 INSERT 操作先缓冲,批量合并写入
- 二次写(Double Write):防止部分写失效(页写了一半系统崩溃)
- 自适应哈希索引(AHI):InnoDB 自动对热点页建立哈希索引
- 预读(Read Ahead):预测即将访问的页,提前加载到 Buffer Pool