引言
本文从底层视角详细解析了一条 SELECT 语句在 MySQL 中的完整执行流程,涵盖连接建立、查询缓存、SQL 解析、执行计划生成以及执行器与存储引擎的协作机制,深入解释了 MySQL 优化器的工作原理和关键设计取舍,适合作为理解 MySQL 内核执行机制和面试高频题的系统性参考。
为什么要搞懂 SELECT 的执行过程?
在日常开发中,我们几乎每天都在写SELECT语句,但大多数人只停留在“会写 SQL”这个层面:
SELECT * FROM user WHERE id = 1;但在实际中,或者在性能优化、慢 SQL 排查、数据库异常分析时,更应该关心的是:
- MySQL是如何处理这条 SQL 的?
- SQL 在 MySQL 内部经历了哪些模块?
- 优化器是如何选择索引的?
- 为什么
EXPLAIN能看到执行计划? - 为什么 MySQL 8.0 直接移除了查询缓存?
理解一条 SELECT 的执行流程,是理解 MySQL 内核的起点。
整体执行流程总览
一条SELECT语句,在 MySQL 中大致会经历以下几个阶段:
连接器(Connection)
查询缓存(Query Cache,8.0 已移除)
解析器(Parser)
词法分析
语法分析
执行器阶段
预处理(Prepare)
优化(Optimize)
执行(Execute)
下面我们按时间顺序逐层拆解。
第一步:连接器 —— 建立客户端与 MySQL 的连接
MySQL 是如何被连接的?
当你在 Linux 或 macOS 终端执行:
mysql -h 127.0.0.1 -u root -p本质上发生了以下事情:
(1)TCP 三次握手
MySQL基于 TCP 协议通信,客户端与 MySQL Server 之间会先完成 TCP 三次握手:
客户端 -> SYN 服务端 -> SYN + ACK 客户端 -> ACK连接成功后,才进入 MySQL 协议层。
(2)身份认证
连接器会做以下校验:
- 校验用户名
- 校验密码
- 校验来源主机
- 加载该用户的权限信息
这些权限信息会在连接建立时一次性读取。
⚠️重要细节
即使你在连接后修改了用户权限,这个连接内的权限也不会立刻生效,需要重新建立连接。
(3)连接管理
每个连接对应一个线程
连接数受
max_connections控制连接长期不释放,会导致连接资源浪费
第二步:查询缓存(Query Cache)—— 已被淘汰的设计
⚠️ MySQL 8.0已经彻底移除查询缓存
但理解它为什么失败,非常有价值。
查询缓存的设计思路
查询缓存采用Key-Value形式:
Key:SQL 字符串(完全一致)
Value:查询结果集
流程是:
收到 SQL → 判断是否命中缓存 → 命中:直接返回结果 → 未命中:继续执行 SQL为什么查询缓存命中率极低?
举个例子:
SELECT * FROM user WHERE id = 1; SELECT * FROM user WHERE id=1;哪怕只是一个空格不同,缓存都无法复用。
更致命的是:
- 只要表有任何一条数据发生变化
- 该表相关的缓存全部失效
在高并发、频繁写入的系统中:
查询缓存 = 缓存了个寂寞
为什么 MySQL 8.0 移除了它?
- 维护成本高
- 锁竞争严重
- 实际收益极低
结论:
查询缓存是一个“设计上很美好,实践中很失败”的功能。
第三步:解析器 —— SQL 是如何被“看懂”的?
解析器负责把字符串形式的 SQL,转成 MySQL 能理解的结构。
词法分析(Lexical Analysis)
将 SQL 字符流拆分成一个个Token。
示例 SQL:
SELECT name FROM user WHERE id = 1;词法分析后大致得到:
| Token 类型 | 内容 |
|---|---|
| KEYWORD | SELECT |
| IDENTIFIER | name |
| KEYWORD | FROM |
| IDENTIFIER | user |
| KEYWORD | WHERE |
| IDENTIFIER | id |
| OPERATOR | = |
| NUMBER | 1 |
语法分析(Syntax Analysis)
在词法分析的基础上,解析器会:
- 校验 SQL 是否符合 MySQL 语法规则
- 构建语法树(AST,Abstract Syntax Tree)
如果 SQL 不合法,例如:
SELEC name FROM user;会直接在这一步报错:
You have an error in your SQL syntax第四步:执行 SQL(核心)
真正“干活”的阶段,分为三步:
Prepare → Optimize → ExecutePrepare:预处理阶段
主要做静态检查:
- 表是否存在
- 字段是否存在
- 权限是否满足
- 将
SELECT *展开为具体字段
例如:
SELECT * FROM user;会被展开为:
SELECT id, name, age, email FROM user;Optimize:优化器阶段(灵魂)
优化器做什么?
- 选择使用哪个索引
- 决定表的访问顺序
- 判断是否使用:
全表扫描
索引扫描
覆盖索引
成本模型
MySQL 优化器是基于成本(Cost)的:
- I/O 成本
- CPU 成本
- 预估扫描行数
最终选择成本最低的执行计划。
使用 EXPLAIN 查看执行计划
EXPLAIN SELECT * FROM user WHERE id = 1;常见字段含义:
| 字段 | 含义 |
|---|---|
| type | 访问方式(const、ref、range、ALL) |
| key | 实际使用的索引 |
| rows | 预估扫描行数 |
| Extra | 额外信息(Using index、Using filesort) |
Execute:执行器阶段
执行器根据优化器生成的执行计划:
- 调用存储引擎接口(如 InnoDB)
- 按索引或全表扫描读取数据
- 逐行判断
WHERE条件 - 返回结果给客户端
总结:一条 SELECT 的完整生命周期
客户端 ↓ TCP 连接 + 身份认证(连接器) ↓ 查询缓存(8.0 已移除) ↓ 解析器(词法 + 语法) ↓ 预处理(表、字段、权限) ↓ 优化器(选择索引、执行计划) ↓ 执行器(调用存储引擎) ↓ 返回结果