mysql作为最常用的关系型数据库之一,其内部处理查询语句的流程有着明确的分工和顺序,理解这个流程能帮助我们更高效地排查sql问题和优化查询性能。整个查询执行过程主要由解析器、优化器、执行引擎三个核心组件协同完成,每个组件负责不同的处理环节。

mysql查询执行的整体流程
一条查询语句从客户端发送到mysql服务端,到最终返回结果,整体会经过以下几个核心步骤:
- 客户端向mysql服务端发送sql查询语句
- 服务端接收sql后,先由查询缓存模块尝试匹配缓存(mysql8.0已移除该模块)
- 解析器对sql进行词法分析和语法分析,生成解析树
- 预处理器对解析树进行语义校验,确认表、字段是否存在,权限是否合法
- 优化器基于成本模型,对解析树进行优化,生成最优执行计划
- 执行引擎根据执行计划,调用底层存储引擎的接口获取数据
- 执行引擎将获取到的数据进行处理后返回给客户端
解析器的工作流程
解析器是mysql处理查询语句的第一道关卡,主要负责将文本形式的sql转换为mysql能理解的结构化数据,整个过程分为两个子阶段:
词法分析阶段
词法分析器会将sql语句拆分成一个个独立的词法单元,比如关键字、表名、字段名、运算符、常量等。例如对于查询语句SELECT id,name FROM user WHERE age > 18,词法分析后会识别出SELECT是查询关键字,id和name是查询字段,user是表名,WHERE是条件关键字,age是条件字段,>是比较运算符,18是常量值。
语法分析阶段
语法分析器会基于mysql的语法规则,检查词法单元的组合是否符合语法规范,如果不符合会直接返回语法错误。如果语法合法,会生成一棵对应的解析树,解析树是后续处理的基础结构。
我们可以通过以下方式查看解析器的处理结果,开启mysql的语法分析日志:
-- 开启语法分析日志,仅用于调试,生产环境不建议开启 SET GLOBAL log_slow_admin_statements = ON; SET GLOBAL slow_query_log = ON; -- 执行一条查询语句后,可以在慢查询日志中看到解析相关的信息 SELECT id FROM test_table WHERE id = 1;
预处理器的工作流程
解析器生成的解析树只是保证了sql的语法正确,并不代表语义合法,预处理器的职责就是对解析树进行语义校验,主要包含以下检查项:
- 检查查询中涉及的表是否存在于当前数据库中
- 检查查询中涉及的字段是否存在于对应的表中
- 检查当前用户是否对涉及的表有对应的查询权限
- 将解析树中的*符号展开为对应的所有字段名
如果预处理器检查到不合法的内容,会直接返回对应的错误信息,比如表不存在、字段不存在或者权限不足等。
优化器的工作流程
优化器是mysql查询处理中非常核心的组件,它的作用是基于解析树和预处理后的结果,生成一个执行成本最低的执行计划。优化器主要会做以下几类优化:
逻辑优化
逻辑优化主要是基于sql的逻辑等价性对查询进行改写,比如:
- 移除不必要的括号
- 常量传递,比如WHERE a=5 AND b=a可以改写为WHERE a=5 AND b=5
- 移除不必要的条件,比如WHERE 1=1 AND a>10可以去掉1=1的条件
- 子查询优化,将部分子查询改写为连接查询
物理优化
物理优化主要是选择最优的索引和表连接顺序,比如:
- 分析每个可用索引的区分度,选择最优的索引
- 确定多表连接时的表连接顺序,减少中间结果集的大小
- 决定是否使用临时表、是否使用文件排序等
我们可以通过EXPLAIN命令查看优化器生成的执行计划,示例如下:
-- 创建测试表和索引
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
age INT,
INDEX idx_age (age)
);
-- 查看查询的执行计划
EXPLAIN SELECT id,name FROM user WHERE age > 18;
执行上述语句后,会返回优化器生成的执行计划,其中type列显示访问类型,key列显示使用的索引,rows列显示预估扫描的行数,这些信息可以帮助我们判断优化器的选择是否合理。
执行引擎的工作流程
执行引擎拿到优化器生成的执行计划后,会按照执行计划的步骤,调用底层存储引擎提供的接口来完成数据的查询操作,主要流程如下:
- 如果查询涉及索引,调用存储引擎的索引扫描接口获取数据
- 如果查询需要全表扫描,调用存储引擎的全表扫描接口获取数据
- 如果查询有条件过滤,对获取到的数据进行条件匹配,过滤掉不符合条件的数据
- 如果查询有排序、分组、聚合等操作,对过滤后的数据进行对应的处理
- 将最终处理完成的数据返回给客户端
我们可以通过一个简单的存储引擎接口调用的伪代码来理解执行引擎的工作:
// 伪代码,模拟执行引擎调用存储引擎的过程
public List<Row> execute(ExecutionPlan plan) {
List<Row> result = new ArrayList<>();
// 获取执行计划中的表和信息
Table table = plan.getTable();
Index index = plan.getIndex();
Condition condition = plan.getCondition();
// 调用存储引擎接口获取数据
Iterator<Row> rowIterator;
if (index != null) {
// 使用索引扫描
rowIterator = table.getStorageEngine().scanByIndex(index, condition);
} else {
// 全表扫描
rowIterator = table.getStorageEngine().fullScan(condition);
}
// 处理数据
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
if (condition.match(row)) {
result.add(row);
}
}
return result;
}
常见查询的执行顺序说明
对于复杂的查询语句,mysql的内部执行顺序和我们书写sql的顺序并不一致,标准的sql书写顺序和执行顺序对比如下:
| sql书写顺序 | mysql内部执行顺序 |
|---|---|
| SELECT | FROM |
| FROM | ON |
| JOIN | JOIN |
| ON | WHERE |
| WHERE | GROUP BY |
| GROUP BY | HAVING |
| HAVING | SELECT |
| ORDER BY | DISTINCT |
| LIMIT | ORDER BY |
| LIMIT |
这个执行顺序也体现了mysql处理查询的逻辑:先确定数据来源,再过滤数据,之后进行分组聚合,最后选择返回字段、排序和限制返回行数。
总结
mysql查询执行顺序是解析器、优化器、执行引擎协同工作的结果,解析器负责语法和语义校验,生成合法的解析树;优化器基于成本模型生成最优执行计划;执行引擎按照执行计划调用存储引擎接口获取数据并返回结果。理解这个流程不仅能帮助我们写出更合理的sql,还能在遇到查询性能问题时,通过EXPLAIN等工具快速定位是解析、优化还是执行环节的问题,从而有针对性地进行优化。