MySQL 整体架构
想象一下:一条 SQL 语句从客户端发出,到返回结果,MySQL 内部到底经历了什么?
很多人只会说「执行 SQL」,但追问下去,连接池怎么工作?查询缓存还在吗?优化器怎么生成执行计划?存储引擎怎么读写数据?
今天,我们把这套流程彻底拆开来看。
三层架构概览
MySQL 采用的是经典的三层架构:
┌─────────────────────────────────────────────────────────────┐
│ 连接层 │
│ (连接器、管理连接、权限验证) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 服务层 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 缓存区 │ │ 解析器 │ │ 优化器 │ │ 执行器 │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 存储引擎层 │
│ (插件式架构:InnoDB、MyISAM、Memory...) │
└─────────────────────────────────────────────────────────────┘为什么这样设计?
分层的好处是解耦——连接层处理网络通信,服务层负责 SQL 解析和优化,存储引擎层负责数据存取。各层可以独立演进,这也是 MySQL 支持多种存储引擎的原因。
连接层:别让连接成为瓶颈
连接层做的事情很简单但很重要:
连接管理
java
// 常见的连接问题代码
Connection conn = DriverManager.getConnection(url, user, password);
// 问题:每次查询都创建新连接,连接开销巨大MySQL 连接分为两种模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 短连接 | 每次查询创建连接,查询完关闭 | 低频查询 |
| 长连接 | 建立后保持连接,复用 | 高频查询 |
但长连接也有坑——内存增长
MySQL 长连接默认超时是 wait_timeout(默认 8 小时),超过这个时间客户端没动静,MySQL 会主动关闭连接。但问题是,连接池(如 Druid、HikariCP)可能长时间持有这些「死连接」,导致连接失效。
java
// 正确做法:连接池检测连接有效性
HikariConfig config = new HikariConfig();
config.setConnectionTimeout(30000); // 获取连接超时
config.setIdleTimeout(600000); // 空闲超时
config.setMaxLifetime(1800000); // 连接最大生命周期权限验证
连接建立时,MySQL 会根据 mysql.user 表验证用户名、密码和来源 IP。验证通过后,后续操作都会带着这个用户的权限信息。
服务层:SQL 的「大脑」
服务层是 MySQL 最复杂的部分,包含多个组件协同工作。
查询缓存(MySQL 8.0 已移除)
MySQL 5.7 及之前版本有查询缓存:
查询 SQL → 检查缓存 → 命中?→ 直接返回结果
↓ 否
解析 → 优化 → 执行 → 存入缓存 → 返回为什么 MySQL 8.0 移除了查询缓存?
因为它有两个致命问题:
- 写失效:任何表数据变更,相关缓存全部失效。写入频繁时,缓存反而成为负担。
- 内存碎片:缓存管理本身消耗资源,碎片化严重。
MySQL 8.0 之后,建议在应用层做缓存(如 Redis),更可控。
解析器:SQL 怎么变成「可执行的东西」?
sql
SELECT name FROM users WHERE age > 18;解析器的工作:
- 词法分析:把 SQL 拆成一个个 token(关键字、标识符、运算符)
- 语法分析:根据语法规则构建解析树(AST)
- 语义检查:检查表名、列名是否存在,数据类型是否匹配
java
// 解析器输出大概长这样(简化版)
SelectStmt {
columns: [Column(name)]
from: Table(users)
where: Condition(age > 18)
}优化器:怎么执行最快?
优化器是服务层的核心,它会根据成本模型生成最优执行计划。
sql
-- 这两个写法等价,但性能可能差很多
SELECT * FROM users WHERE age > 18 AND name LIKE '张%';
SELECT * FROM users WHERE name LIKE '张%' AND age > 18;
-- 优化器会帮你调整顺序,选择先查索引优化器考虑的因素:
- 表的统计信息(行数、数据分布)
- 索引可用性
- 每种执行方式的成本估算
- 并行度
优化器不是万能的——统计信息不准确时,可能选错执行计划。
执行器:真正干活的地方
执行器根据优化器生成的执行计划,调用存储引擎接口读写数据。
java
// 执行器大致流程(伪代码)
public List<Object> execute(SelectStmt stmt) {
// 1. 检查用户对表的权限
checkPermissions(stmt.table);
// 2. 调用存储引擎读取数据
Iterator<Row> rows = engine.scan(stmt.whereCondition);
// 3. 执行投影(SELECT name)和过滤(WHERE age > 18)
List<Object> result = new ArrayList<>();
while (rows.hasNext()) {
Row row = rows.next();
if (evaluateCondition(row, stmt.whereCondition)) {
result.add(project(row, stmt.columns));
}
}
return result;
}存储引擎层:数据怎么存、怎么取?
MySQL 采用插件式存储引擎架构,不同引擎有不同的特性。
常见存储引擎
| 引擎 | 事务支持 | 锁粒度 | 适用场景 |
|---|---|---|---|
| InnoDB | ✅ | 行锁 | 默认,生产环境首选 |
| MyISAM | ❌ | 表锁 | 只读场景、日志表 |
| Memory | ❌ | 表锁 | 临时表、缓存 |
| Archive | ❌ | 表锁 | 归档存储 |
InnoDB 为什么是默认引擎?
- 支持事务(ACID)
- 支持行锁,并发性能好
- 支持崩溃恢复
- 支持外键
MySQL 5.5.5 之后,InnoDB 成为默认引擎,之前是 MyISAM。
一条 SQL 的完整旅程
客户端
│
▼
【连接层】验证连接、分配线程
│
▼
【服务层 - 查询缓存】检查缓存(如果有)
│
▼
【服务层 - 解析器】词法分析、语法分析
│
▼
【服务层 - 预处理器】语义检查
│
▼
【服务层 - 优化器】生成执行计划
│
▼
【服务层 - 执行器】调用存储引擎
│
▼
【存储引擎层】读写数据
│
▼
返回结果给客户端面试追问方向
- MySQL 8.0 为什么移除查询缓存?
- 优化器生成的执行计划一定是最优的吗?
- 服务层和存储引擎层是怎么交互的?
- 如果让你设计一个查询优化器,你会考虑哪些因素?
