MySQL 一条 SQL 的执行全流程

从客户端发起请求到返回结果,完整拆解 MySQL Server 各层架构组件如何协同工作

客户端层
Server 层(连接管理)
Server 层(查询处理引擎)
存储引擎层
🏗️ MySQL 整体架构
点击左侧各层查看详细说明。MySQL 采用可插拔存储引擎架构,分为四层。
客户端层
Connector / API
Client
Server 层 — 连接管理
连接池 / 鉴权 / 线程管理
Server
Server 层 — 查询引擎
解析器 → 优化器 → 执行器
Engine
存储引擎层
InnoDB / MyISAM / Memory …
Storage

🏗️ MySQL 架构总览

点击左侧各层查看详细说明

MySQL 采用经典的分层 + 可插拔存储引擎架构,主要分为四层:

  • 客户端层:各种连接器(JDBC、ODBC、mysql client、PHP 驱动等),负责建立和管理与 MySQL Server 的连接
  • Server 层(连接管理):管理客户端连接、认证鉴权、线程管理、连接池等基础服务
  • Server 层(查询引擎):核心组件——查询缓存、解析器(Parser)、优化器(Optimizer)、执行器(Executor),处理所有 SQL 逻辑
  • 存储引擎层:真正负责数据的读写和存储,Server 层通过 Handler API 与之交互,可插拔替换

💡 Server 层覆盖了大多数核心功能(权限判断、查询处理等),存储引擎层专注于数据的存取。这种分层让不同存储引擎可以无缝切换。

SELECT e.name, e.age, d.dept_name
FROM employee e JOIN department d ON e.dept_id = d.id
WHERE e.age > 25 AND d.dept_name = 'engineering'
ORDER BY e.age DESC LIMIT 10;

↑ 以这条 JOIN 查询为例,走完完整执行流程

🔄 SQL 执行完整流程
从客户端发起连接到最终返回结果集,经过 13 个关键步骤。点击任意步骤展开详细说明。
▼ 点击每个步骤可展开详细说明
1
客户端发送连接请求
客户端层
应用程序通过 TCP/IP(或 Socket)向 MySQL Server 发起连接

客户端(如 Java 应用、MySQL CLI、Navicat 等)通过 MySQL 协议与 Server 建立 TCP 连接。默认端口 3306,支持 UNIX Socket(本机)和命名管道(Windows)。

连接时需要提供:主机地址、端口、用户名、密码。MySQL 支持多种认证插件:mysql_native_passwordcaching_sha2_password 等。

2
连接器(Connector)处理
Server 层
验证身份、分配线程、管理连接生命周期

连接器(Connector)是 Server 层的第一个组件,负责:

身份认证:校验用户名和密码,查询 mysql.user 权限表。若认证失败返回 Access denied

权限获取:认证成功后,从权限表中读取该用户的全局权限。后续若有 USE database,还会读取库级权限;访问具体表时读取表级权限

分配线程:从线程池中分配一个线程(或新建线程)来处理该连接的所有请求。MySQL 采用每个连接一个线程(one-thread-per-connection)模型

空闲管理:连接空闲超过 wait_timeout(默认 8 小时)后自动断开。可通过 show processlist 查看当前所有连接

3
查询缓存(Query Cache)
Server 层
检查是否有完全相同的查询结果缓存(MySQL 8.0 已移除此功能)

查询缓存以SQL 文本的哈希值为 Key,查询结果集为 Value,缓存在内存中。

匹配条件极其严格:SQL 必须完全一致(包括空格、大小写),且涉及的表没有发生任何数据变更。

缓存失效代价很高——任何一个表的任何数据变更,都会清空该表的所有查询缓存。在高写入场景下,缓存命中率极低,反而成为性能瓶颈。

⚠️ 已废弃 MySQL 8.0 已完全移除查询缓存功能。替代方案:使用 Redis 等外部缓存系统。

4
解析器 — 词法分析(Lexer)
查询引擎
将 SQL 文本拆分为一个个「词」(Token)

词法分析器(Lexer/Scanner)将 SQL 字符串拆解为一系列 Token

例如 SELECT name FROM employee WHERE age > 25 会被拆为:

SELECT name FROM employee WHERE age > 25

每个 Token 有类型标识:SELECT 是保留字,name 是标识符,25 是数字字面量,> 是运算符。

如果 SQL 中包含无法识别的字符,在此阶段就会报错You have an error in your SQL syntax

5
解析器 — 语法分析(Parser)
查询引擎
将 Token 序列组织成抽象语法树(AST)

语法分析器(Parser)根据 MySQL 预定义的语法规则(BNF 文法),将 Token 序列构建成一棵 抽象语法树(AST,Abstract Syntax Tree)

AST 的节点类型包括:Select_stmtExprTable_refWhere_clauseOrder_by 等,完整表达了 SQL 的语法结构。

此阶段还会做基本的语法合法性检查:比如 SELECT 缺少 FROM 子句(在某些上下文中)、表达式不完整、缺少右括号等。

💡 注意 语法分析只管语法对不对,不管语义(表存不存在、字段存不存在)。

6
预处理器(Preprocessor)
查询引擎
语义检查:表是否存在、字段是否存在、权限校验

预处理器对 AST 进行语义分析

表/列存在性检查:查询数据字典(information_schema),验证 SQL 中引用的表、字段、视图是否存在。不存在则报错:Table 'xxx' doesn't exist

权限检查:验证当前用户对涉及的表是否有 SELECT 权限。无权限则报错:SELECT command denied

语义合理性:检查 WHEREGROUP BY 等子句是否合法,列别名是否能被正确解析

隐式类型转换:标记需要隐式转换的表达式,为后续优化提供信息

7
查询优化器(Optimizer)
查询引擎
基于代价模型(CBO)选择最优执行计划

优化器是 MySQL 的核心大脑,负责将逻辑执行计划转化为物理执行计划。它基于代价模型(Cost-Based Optimizer,CBO),选择代价最低的执行路径。

优化器的主要工作:

选择索引 决定使用哪个索引(或全表扫描),基于索引的选择性、基数(Cardinality)等统计信息
JOIN 顺序优化 对多表 JOIN,决定表的驱动顺序(小表驱动大表),考虑不同的 JOIN 算法(NLJ、BNLJ、Hash Join)
子查询优化 将相关子查询转为半连接(Semi-join)、物化(Materialization)或派生表合并
ORDER BY 优化 判断是否能利用索引避免排序(filesort),或是否需要临时表
条件优化 常量传播、等值传递、去除恒真/恒假条件、范围合并
LIMIT 优化 提前终止扫描,配合索引下推减少回表次数

可通过 EXPLAIN / EXPLAIN ANALYZE 查看优化器最终选择的执行计划。MySQL 8.0 引入了直方图(Histogram),为优化器提供更精确的数据分布统计。

8
生成执行计划(Execution Plan)
查询引擎
将优化后的方案生成可执行的物理操作序列

优化器输出的是一个执行计划,它是一棵由物理操作符组成的树。常见操作符包括:

seq_scan 全表扫描  index_scan 索引扫描  ref 索引查找  range 范围扫描  nested_loop 嵌套循环连接

执行计划决定了数据访问路径、JOIN 策略、排序方式、临时表使用等所有执行细节。

9
执行器(Executor)调用存储引擎
查询引擎
按执行计划逐步操作,通过 Handler API 与存储引擎交互

执行器是 Server 层与存储引擎层之间的桥梁。它按照执行计划,通过 Handler API 逐行(或批量)调用存储引擎获取数据。

执行器的工作流程(以我们的 JOIN 查询为例):

① 先从 department 表查询 dept_name = 'engineering' 的记录(调用 handler::index_read

② 拿到 dept_id 后,再从 employee 表中查找匹配记录(调用 handler::index_readhandler::rnd_next

③ 对每条满足条件的记录,执行器做进一步的 WHERE 条件过滤(有些条件无法利用索引)

④ 按 age DESC 排序,取前 10 条,返回结果集

💡 Server 层过滤 有些 WHERE 条件无法下推到存储引擎,需要执行器在 Server 层做二次过滤。这就是为什么有时候看到 Using where(执行器过滤)。

10
存储引擎 — Buffer Pool 交互
存储引擎层
先查内存缓冲池,命中则直接返回,未命中则从磁盘加载

InnoDB 为例,数据读取流程:

查找 Buffer Pool:InnoDB 维护了一个大的内存缓冲池(innodb_buffer_pool_size,通常设为物理内存的 60%-80%),存储数据页和索引页

缓存命中:如果请求的数据页已在 Buffer Pool 中,直接从内存读取,速度极快

缓存未命中:如果不在 Buffer Pool 中,触发页面读取请求。InnoDB 通过预读(Read-Ahead)机制,一次读取相邻的多个页面到 Buffer Pool

LRU 淘汰:Buffer Pool 满时,基于改进的 LRU 算法(区分 Young 区和 Old 区,防止全表扫描污染缓存)淘汰冷页面

11
存储引擎 — B+Tree 索引查找
存储引擎层
通过 B+Tree 索引结构快速定位数据页和记录

如果是索引扫描,InnoDB 通过 B+Tree 结构查找:

① 从 B+Tree 根节点开始,逐层比较键值,定位到叶子节点。B+Tree 一般 3-4 层即可存储千万级数据(因为每个非叶子节点可存储数百个指针)

② 在叶子节点层面,通过二分查找定位到第一条匹配记录

聚簇索引(Clustered Index):叶子节点直接存储完整数据行,查找一次即可

二级索引(Secondary Index):叶子节点存储主键值,需要回表(Bookmark Lookup)到聚簇索引再查一次完整行数据

索引下推 MySQL 5.6+ 的 ICP(Index Condition Pushdown)允许将部分 WHERE 条件下推到存储引擎层过滤,减少回表次数。

12
存储引擎 — 磁盘 I/O 与事务保障
存储引擎层
涉及磁盘读写时,通过 redo log / undo log 保障 ACID

当 Buffer Pool 未命中,需要读取磁盘数据文件(.ibd):

磁盘读取:操作系统将数据页从磁盘读入 InnoDB Buffer Pool。InnoDB 的页大小默认 16KB

后台线程:InnoDB 有多个后台线程负责异步 I/O(innodb_read_io_threads)、脏页刷新(Page Cleaner Thread)、redo log 刷盘(Log Thread)等

写操作额外保障(本次查询为 SELECT,但写操作涉及):

Redo Log:WAL(Write-Ahead Logging),先写日志再写磁盘,保证持久性(Durability)

Undo Log:记录数据修改前的值,保证原子性(Atomicity)和 MVCC

Change Buffer:缓存二级索引的修改,减少随机 I/O

Doublewrite Buffer:防止页撕裂(partial write),每次写数据页先写到双写缓冲区

13
返回结果集给客户端
客户端层
执行器将结果集逐行发送回客户端连接

执行器将处理好的结果集,通过客户端连接逐行(或按包)发送回去:

增量返回:MySQL 不是等所有数据都查完了才返回,而是查到一部分就发送一部分。这对客户端体验和内存占用都很友好

结果集格式:按 MySQL 协议封装为文本或二进制结果集包(取决于是否使用 prepared statement)

状态更新:发送完毕后附带一个 OK 包,包含 affected_rowswarnings 等状态信息

连接保持:连接不会自动关闭,进入空闲状态,等待下一条 SQL。直到客户端断开或超时

💡 长连接 vs 短连接 短连接每次都要重新走步骤 1-3(握手+认证),开销大。生产环境建议使用数据库连接池复用连接。

💾 存储引擎对比
MySQL 的可插拔存储引擎架构允许同一实例中使用不同存储引擎。

🔥 InnoDB(默认引擎)

支持事务(ACID)、行级锁、MVCC、外键、崩溃恢复。聚簇索引,B+Tree 结构。适合高并发 OLTP 场景,是绝大多数场景的首选。

📄 MyISAM

不支持事务和行级锁,仅表级锁。支持全文索引、压缩表。查询性能在只读场景下较高,但写入时锁整表。MySQL 5.5 后不再默认。

⚡ Memory

数据全部存储在内存中,表结构持久化。速度极快,但重启后数据丢失。支持 Hash 和 B-Tree 索引。适合临时表、缓存表。

📊 Archive

专为海量归档数据设计,支持极高压缩比(zlib)。仅支持 INSERT 和 SELECT,不支持索引。适合日志存储、历史数据归档。

🔗 NDB Cluster

分布式存储引擎,数据自动分片到多个数据节点。支持高可用、无单点故障。适合需要高可用和水平扩展的场景。

📑 CSV

数据以 CSV 文件格式存储,表结构在 .frm 文件中。可直接用文本编辑器查看数据。不支持索引,适合数据交换场景。

🔑 关键概念速查
SQL 执行流程中频繁出现的重要概念。

Handler API

Server 层与存储引擎层的接口。每张表在被打开时都会被分配一个 Handler 实例。执行器通过 handler::read_rowhandler::write_rowhandler::index_read 等方法与存储引擎交互,不关心底层具体实现。

MVCC 多版本并发控制

InnoDB 通过 Undo Log + Read View 实现 MVCC,让读写操作互不阻塞。每个事务看到的是数据的一个快照版本。Read View 判断 Undo Log 版本链中哪些版本对当前事务可见。

Redo Log 与 WAL

WAL(Write-Ahead Logging)策略:先写 Redo Log(顺序写,快),再异步刷脏页到磁盘(随机写,慢)。崩溃恢复时通过 Redo Log 重放保证数据不丢失。Redo Log 固定大小,循环写入。

EXPLAIN 执行计划

通过 EXPLAIN 查看优化器的执行计划决策。关键字段:type(访问类型)、key(使用的索引)、rows(预估扫描行数)、Extra(额外信息如 Using index / Using temporary / Using filesort)。