MySQL 执行一条 SQL 的内部完整流程
以一条查询 SQL 为例:
sql
SELECT * FROM orders WHERE user_id = 100 AND status = 'PAID' ORDER BY create_time DESC LIMIT 10;
阶段一:连接器(Connector)
客户端 → 连接器
- 验证用户名、密码、主机权限
- 建立 TCP 连接,分配线程
- 维护连接状态(
SHOW PROCESSLIST看到的就是这里) - 连接建立后,权限判断结果会缓存在连接上下文中,后续该连接内的查询不再重复校验权限
阶段二:查询缓存(MySQL 8.0 已移除)
查询 → 查询缓存
- 以 SQL 文本为 key,查缓存中是否有结果
- 命中 → 直接返回,跳过后续所有阶段
- 未命中 → 继续往下走
⚠️ MySQL 8.0 已经彻底移除了查询缓存,因为失效太频繁(表有任何更新就清空该表所有缓存),命中率极低。
阶段三:分析器(Analyzer)
SQL文本 → 词法分析 → 语法分析 → 解析树
词法分析:把 SQL 字符串拆成 token
"SELECT * FROM orders WHERE user_id = 100"
→ [SELECT] [*] [FROM] [orders] [WHERE] [user_id] [=] [100]
语法分析:根据语法规则,构建解析树
SELECT
/ | \
columns FROM WHERE
* orders user_id = 100
| status = 'PAID'
ORDER BY LIMIT
create_time 10
- 这一步会检查 SQL 语法是否正确
- 表名、列名是否存在,在这一步还不会检查(那是优化器的事)
阶段四:预处理器(Preprocessor)
解析树 → 预处理
- 展开
SELECT *→ 查表的元数据,替换成具体列名 - 检查表名、列名是否存在
- 检查权限
- 处理通配符、默认值等
阶段五:优化器(Optimizer)--- 最核心的阶段
解析树 → 查询优化 → 执行计划
优化器要做的决策:
| 决策 | 举例 |
|---|---|
| 选择哪个索引 | 有 idx_user_id 和 idx_status,用哪个? |
| 多表 JOIN 顺序 | A JOIN B JOIN C,先 JOIN 哪个? |
| 是否使用索引 | 走索引快还是全表扫描快? |
| 子查询优化 | 子查询能否转成 JOIN? |
| 排序优化 | 能否利用索引的有序性,避免 filesort? |
优化器基于**代价模型(Cost-Based)**做决策:
IO代价 = 需要读取的页数 × 单页读取代价
CPU代价 = 需要检查的行数 × 单行比较代价
总代价 = IO代价 + CPU代价
哪个执行计划的总代价最低,就选哪个
输出一个执行计划,比如:
→ 使用 idx_user_id 索引查找 user_id = 100 的行
→ 在索引结果中过滤 status = 'PAID'
→ 按 create_time 排序
→ 取前 10 条
阶段六:执行器(Executor)
执行计划 → 调用存储引擎 → 返回结果
-
先检查对目标表是否有 SELECT 权限
-
调用存储引擎接口,按执行计划逐步执行
执行器 存储引擎(InnoDB)
| |
|-- 调用 ha_init() 打开表 --------→ | 打开表,加载表结构
| |
|-- 调用 ha_index_init(idx) ------→ | 初始化 idx_user_id 索引
| |
|-- 调用 ha_index_read(100) ------> | 在B+树中找到 user_id=100 的记录
| |
|-- 过滤 status='PAID' <---------- | 返回匹配行
| |
|-- 调用 ha_index_read() 继续 ----→ | 继续扫描下一条
| |
|-- 收集够10条,排序,返回结果 |
阶段七:存储引擎层(InnoDB)
存储引擎内部做的事:
1. 从 Buffer Pool 中查找数据页
├─ 命中 → 直接返回(内存读取,微秒级)
└─ 未命中 → 从磁盘读取数据页到 Buffer Pool(毫秒级)
2. 行锁处理
├─ SELECT ... FOR UPDATE → 加排他锁(X锁)
├─ SELECT ... LOCK IN SHARE MODE → 加共享锁(S锁)
└─ 普通 SELECT → 不加锁(MVCC 读)
3. MVCC(多版本并发控制)
├─ 读取 undo log 中的历史版本
└─ 根据 Read View 判断哪一版对当前事务可见
4. 返回数据给执行器
完整流程图
客户端
│
▼
┌─────────────────────┐
│ 连接器 │ 验证身份、建立连接
└─────────┬───────────┘
▼
┌─────────────────────┐
│ 查询缓存(8.0移除) │ 命中则直接返回
└─────────┬───────────┘
▼
┌─────────────────────┐
│ 分析器 │ 词法分析 → 语法分析 → 解析树
└─────────┬───────────┘
▼
┌─────────────────────┐
│ 预处理器 │ 展开*、检查表名列名、权限
└─────────┬───────────┘
▼
┌─────────────────────┐
│ 优化器 │ 选索引、定JOIN顺序、生成执行计划
└─────────┬───────────┘
▼
┌─────────────────────┐
│ 执行器 │ 按执行计划调用存储引擎
└─────────┬───────────┘
▼
┌─────────────────────┐
│ 存储引擎(InnoDB) │ Buffer Pool → 行锁 → MVCC → 返回数据
└─────────┬───────────┘
▼
返回结果给客户端
📌 一句话总结
一条 SQL 从进入 MySQL 到返回结果,经过 连接器 → 查询缓存 → 分析器 → 预处理器 → 优化器 → 执行器 → 存储引擎 共 7 个阶段。其中优化器是最核心的,它决定走哪个索引、怎么 JOIN、要不要排序,直接决定了 SQL 的快慢。
你是在排查某条具体 SQL 的性能问题吗?可以把 SQL 和执行计划(EXPLAIN 结果)贴给我,我帮你分析优化器选了什么样的执行计划、有没有优化空间。