Skip to content

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 移除了查询缓存?

因为它有两个致命问题:

  1. 写失效:任何表数据变更,相关缓存全部失效。写入频繁时,缓存反而成为负担。
  2. 内存碎片:缓存管理本身消耗资源,碎片化严重。

MySQL 8.0 之后,建议在应用层做缓存(如 Redis),更可控。

解析器:SQL 怎么变成「可执行的东西」?

sql
SELECT name FROM users WHERE age > 18;

解析器的工作:

  1. 词法分析:把 SQL 拆成一个个 token(关键字、标识符、运算符)
  2. 语法分析:根据语法规则构建解析树(AST)
  3. 语义检查:检查表名、列名是否存在,数据类型是否匹配
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 为什么是默认引擎?

  1. 支持事务(ACID)
  2. 支持行锁,并发性能好
  3. 支持崩溃恢复
  4. 支持外键

MySQL 5.5.5 之后,InnoDB 成为默认引擎,之前是 MyISAM。


一条 SQL 的完整旅程

客户端


【连接层】验证连接、分配线程


【服务层 - 查询缓存】检查缓存(如果有)


【服务层 - 解析器】词法分析、语法分析


【服务层 - 预处理器】语义检查


【服务层 - 优化器】生成执行计划


【服务层 - 执行器】调用存储引擎


【存储引擎层】读写数据


返回结果给客户端

面试追问方向

  • MySQL 8.0 为什么移除查询缓存?
  • 优化器生成的执行计划一定是最优的吗?
  • 服务层和存储引擎层是怎么交互的?
  • 如果让你设计一个查询优化器,你会考虑哪些因素?

基于 VitePress 构建