[小技巧52]从 SQL 到结果:MySQL 8.0 查询执行全流程深度剖析

一、MySQL 查询执行的六大核心阶段

1. 连接建立与认证(Connection & Authentication)

  • 客户端通过 TCP/IP 或 Unix Socket 发起连接。
  • MySQL Server 的 Connection Manager 接收请求,分配线程(或从线程池复用)。
  • 执行用户身份验证(校验用户名、密码、主机白名单),加载权限信息。
  • 若启用了 SSL/TLS,则在此阶段完成加密握手。

注意:自 MySQL 8.0 起,Query Cache 已被彻底移除,因此不再参与后续流程。

2. 查询解析(Parsing)

  • SQL 文本被送入 Parser(语法分析器)
  • 使用 LALR(1) 语法分析器(基于 yacc/bison)进行词法与语法分析。
  • 输出一棵 解析树(Parse Tree),表示 SQL 的结构化形式。
  • 此阶段会检查基本语法错误(如关键字拼写、括号不匹配等)。

示例:

sql 复制代码
SELECT id, name FROM users WHERE age > 25;

→ 被解析为包含 SELECT_LIST、FROM_CLAUSE、WHERE_CLAUSE 的抽象语法树。

3. 预处理(Preprocessing / Resolution)

  • Resolver 模块介入,对解析树进行语义分析:
    • 验证表、列是否存在;
    • 检查用户是否有对应对象的访问权限;
    • 解析视图(若涉及)并展开;
    • 确定字段所属的数据库/表(处理歧义);
    • 类型推导与隐式转换判断。
  • 此阶段生成 Resolved Parse Tree,为优化器提供准确元数据。

4. 查询优化(Optimization)

这是整个流程中最复杂的环节,由 Query Optimizer 主导:

关键子步骤包括:

  • 常量传播与折叠 :如 WHERE id = 1 + 2WHERE id = 3
  • 谓词下推:将过滤条件下推至存储引擎层
  • 索引选择:基于统计信息(cardinality、直方图等)评估可用索引
  • 连接顺序重排:对多表 JOIN 使用动态规划或贪心算法确定最优顺序
  • 访问路径选择:决定使用 index scan、range scan、full table scan 等
  • 成本模型计算:基于 I/O、CPU、内存等估算执行成本(单位:"cost units")

最终输出 执行计划(Execution Plan) ,以 Query Plan Tree 形式表示。

MySQL 8.0 引入了 直方图(Histograms)不可见索引(Invisible Indexes),显著提升了优化器决策精度。

5. 执行引擎处理(Execution)

  • Executor 模块根据执行计划调用 Handler API 与存储引擎交互。
  • 对于 InnoDB(默认引擎):
    • 通过 Buffer Pool 缓存数据页;
    • 执行 B+ 树查找、范围扫描或全表扫描;
    • 应用 MVCC 机制读取符合事务隔离级别的可见版本;
    • 若存在排序(ORDER BY)或分组(GROUP BY),可能触发 临时表(Temp Table)文件排序(Filesort)
  • 结果集逐行构建,流式返回(非一次性加载全部数据)。

6. 结果返回(Result Sending)

  • Executor 将结果通过网络协议(MySQL Protocol)封装;
  • 由连接线程发送回客户端;
  • 客户端驱动(如 JDBC、mysql-connector-python)解析二进制/文本协议,还原为应用可读格式。

二、执行流程图

注意:自 MySQL 8.0 起,Query Cache 已被彻底移除,所以在MySQL8.0没有这个流程。

复制代码
[客户端]
   ↓(通过 TCP 或 Unix Socket 建立连接)
[连接管理器] → [用户身份认证]
   ↓
[接收 SQL 查询语句]
   ↓
[解析器] →(语法检查)→ [生成解析树]
   ↓
[预处理器] →(解析表名、列名及权限校验)→ [生成已解析树]
   ↓
[查询优化器] →(基于成本的优化)→ [生成执行计划]
   ↓
[执行器] ↔ [Handler 接口] ↔ [存储引擎(例如 InnoDB)]
   ↓
[构建结果集]
   ↓
[网络协议层] → [将结果返回给客户端]

各阶段简要说明:

  • 连接管理器:负责建立连接、分配线程、验证用户凭证。
  • 解析器:检查 SQL 语法是否合法,构建抽象语法树。
  • 预处理器:进行语义分析,确认对象存在性与访问权限。
  • 查询优化器:决定最优执行路径(如索引选择、JOIN 顺序)。
  • 执行器 + 存储引擎:实际读取数据、应用过滤、聚合等操作。
  • 网络协议层:将最终结果按 MySQL 协议封装并发送回客户端。

三、各阶段关键组件对比表

阶段 核心组件 输入 输出 涉及模块
连接建立 Connection Manager 网络连接请求 认证后的会话线程 auth, thread pool
解析 Parser SQL 字符串 Parse Tree sql/parser_yystype.h
预处理 Resolver Parse Tree Resolved Tree sql/item.cc, sql/table.cc
优化 Optimizer Resolved Tree Execution Plan sql/optimize.cc, sql/sql_planner.cc
执行 Executor + Handler Execution Plan 行数据流 sql/executor/, storage/innodb/
返回 Protocol Layer 行数据 网络数据包 sql/protocol_classic.cc

注:Query Cache 在 MySQL 8.0 中已移除。

非常好的建议!在技术博客中加入对 SQL 逻辑执行顺序(Logical Query Processing Order)的解析,能极大提升读者对查询语句本质的理解------尤其当他们混淆"书写顺序"与"实际执行顺序"时。下面我将为你补充这一关键内容,并无缝整合进原博客结构。


四、SQL 逻辑执行顺序 vs. 书写顺序

尽管我们在编写 SQL 时通常按以下语法顺序书写:

sql 复制代码
SELECT 
    column_list
FROM table_name
WHERE condition
GROUP BY group_expression
HAVING having_condition
ORDER BY sort_expression
LIMIT row_count;

MySQL(以及所有符合 SQL 标准的数据库)在逻辑上并非按此顺序执行

MySQL 中 SELECT 查询的逻辑执行顺序如下:

步骤 子句 说明
1 FROM 确定数据源(表、视图、JOIN 结果),生成初始虚拟表 VT1
2 WHERE 对 VT1 应用行级过滤,生成 VT2
3 GROUP BY 将 VT2 按分组表达式聚合,生成 VT3(每组一行)
4 HAVING 对 VT3 的分组结果进行过滤,生成 VT4
5 SELECT 计算选择列表(包括表达式、别名、聚合函数),生成 VT5
6 ORDER BY 对 VT5 排序,生成有序结果 VT6
7 LIMIT / OFFSET 截取指定行数,生成最终结果

注意:这是逻辑顺序 ,不代表物理执行路径。优化器可能重排操作以提升性能(如谓词下推),但必须保证结果等价性

示例分析

考虑以下完整查询:

sql 复制代码
SELECT 
    department, 
    COUNT(*) AS employee_count,
    AVG(salary) AS avg_salary
FROM employees
WHERE hire_date > '2020-01-01'
GROUP BY department
HAVING AVG(salary) > 80000
ORDER BY employee_count DESC
LIMIT 5;

执行逻辑分解:

  1. FROM employees

    → 读取 employees 表(可能通过索引或全表扫描)。

  2. WHERE hire_date > '2020-01-01'

    → 过滤出 2020 年后入职的员工(可在存储引擎层完成,若 hire_date 有索引)。

  3. GROUP BY department

    → 按部门分组,为每组计算中间聚合值(count、sum 等)。

  4. HAVING AVG(salary) > 80000

    仅在此阶段才能使用聚合函数;过滤掉平均薪资 ≤8 万的部门。

  5. SELECT ... AS ...

    → 构造输出列(注意:此时 employee_countavg_salary 才被定义,因此 WHEREGROUP BY 中不能引用这些别名)。

  6. ORDER BY employee_count DESC

    → 按聚合后的计数降序排列(可能触发 filesort)。

  7. LIMIT 5

    → 返回前 5 行。

五、面试题

问题一:

MySQL 8.0 中,一条 SELECT 语句是否还会经过 Query Cache?

:不会。MySQL 自 8.0.3 版本起完全移除了 Query Cache 功能。官方认为其在高并发场景下反而成为性能瓶颈,且维护一致性开销大。因此,所有查询均直接进入解析阶段。

问题二:

优化器如何决定是否使用索引?你能举例说明吗?

:优化器基于成本模型(Cost Model) 决策,主要考虑:

  • 索引的选择性(高选择性更优);
  • 数据分布(通过直方图或索引统计);
  • 是否覆盖查询(Covering Index 可避免回表);
  • 排序/分组是否可利用索引有序性。

示例

sql 复制代码
SELECT name FROM users WHERE age > 30;

(age, name) 是联合索引,则可走 Index Only Scan (无需回表),成本远低于全表扫描。但若 age > 30 匹配 90% 的行,优化器可能判定全表扫描更高效(因顺序 I/O 比随机 I/O 快)。

**问题三:

为什么不能在 WHERE 子句中使用聚合函数(如 COUNT、SUM),但可以在 HAVING 中使用?这和查询执行流程有什么关系?**

:这是因为 SQL 的逻辑执行顺序 决定的。WHEREGROUP BY 之前执行,此时数据尚未分组,也未计算聚合值,因此无法引用聚合函数。而 HAVINGGROUP BY 之后执行,作用于已形成的分组结果集,此时聚合值已经生成,故可进行过滤。

从 MySQL 执行流程看:

  • WHERE 条件由 Executor 在访问存储引擎前 应用(可能下推至 InnoDB);
  • 聚合计算(如 COUNT)发生在 Executor 的聚合阶段 ,位于 GROUP BY 处理之后;
  • 因此,优化器在解析阶段就会拒绝 WHERE COUNT(*) > 1 这类语句,报错 "Invalid use of group function"。
相关推荐
2601_949815336 小时前
MySQL输入密码后闪退?
数据库·mysql·adb
_下雨天.6 小时前
MySQL高可用
数据库·mysql
生瓜硬劈..7 小时前
SQL 调优全解:从 20 s 到 200 ms 的 6 步实战笔记
java·笔记·sql
颜颜yan_7 小时前
让数据库学会说“不“——金仓 SQL 防火墙深度解析
数据库·sql
輕華7 小时前
【零基础入门】SQL 核心语法精讲:外键约束与多表查询全解析(进阶篇)
数据库·sql
gjc5927 小时前
零基础OceanBase数据库入门(5):MySQL模式用户创建与权限管理
数据库·mysql·oceanbase
AC赳赳老秦7 小时前
OpenClaw二次开发入门:自定义技能,适配自身工作需求
服务器·数据库·python·mysql·django·deepseek·openclaw
我真会写代码8 小时前
MySQL8聚簇索引与非聚簇索引深度解析:从原理到实战,避开90%的索引踩坑点
mysql·uuid
white-persist8 小时前
【渗透测试 红队】Netcat(NC)渗透实战全指南详解
开发语言·数据库·python·sql·算法·web安全·网络安全