Mysql查询时的执行流程总结
MySQL查询执行流程分为服务器层和存储引擎层,包含8个核心步骤:连接建立→查询缓存→SQL解析→预处理→优化器生成执行计划→执行器调用存储引擎API→数据读取处理→返回结果。关键阶段包括:连接管理器处理身份验证和权限校验;查询缓存(8.0已移除)快速返回命中结果;解析器生成语法树并通过预处理校验语义;优化器基于成本选择最优执行计划;执行器按计划调用存储引擎API获取数据。理解该流程有助于排查慢查
MySQL查询执行全流程
在数据库操作中,查询是最核心、最高频的操作之一。很多开发者只关注SQL语法的正确性,却忽略了MySQL背后的执行逻辑——理解查询执行流程,不仅能帮你快速排查慢查询问题,更能针对性地进行SQL优化和架构设计。
一、查询执行宏观总览
MySQL的查询执行架构采用分层设计,核心分为服务器层(Server Layer)和存储引擎层(Storage Engine Layer)。其中服务器层是所有存储引擎共享的通用模块(如SQL解析、优化、权限校验等),存储引擎层负责数据的物理存储与读取(如InnoDB、MyISAM等,可按需选择)。
宏观上,MySQL查询执行可概括为8个核心步骤:
客户端发送SQL查询请求 → 服务器层接收并验证请求 → 查询缓存(可选,MySQL 8.0已移除) → SQL解析与预处理 → 查询优化器生成执行计划 → 执行器调用存储引擎API → 存储引擎层读取/处理数据 → 服务器层整理结果并返回给客户端
为了更直观理解,以下是MySQL查询执行的架构流程图:
flowchart TD
A[客户端(Client)] --> B[MySQL服务器层(Server Layer)]
B --> B1[连接管理器(身份验证/权限校验)]
B1 --> B2[查询缓存(Query Cache,8.0移除)]
B2 -->|缓存未命中/无缓存| B3[SQL解析器(Parser)]
B3 --> B4[预处理模块(Preprocessor)]
B4 --> B5[查询优化器(Optimizer)]
B5 --> B6[执行器(Executor)]
B6 --> C[存储引擎层(Storage Engine Layer)]
C --> C1[InnoDB/MyISAM等存储引擎]
C1 --> C2[缓冲池(Buffer Pool)/磁盘]
C2 --> C1
C1 --> B6
B6 --> B7[结果集整理(排序/分页/去重)]
B7 --> A
B2 -->|缓存命中| B7[直接返回缓存结果]
二、分阶段详细拆解
阶段1:客户端与MySQL服务器的连接建立
在发送查询SQL之前,客户端需要先与MySQL服务器建立网络连接,这是查询执行的前置准备。
-
客户端发起连接请求
客户端通过mysql命令行、JDBC、Navicat等工具,向MySQL服务器(默认3306端口)发起TCP连接请求,携带核心参数:- 连接地址(IP/域名)、端口号;
- 用户名、密码;
- 目标数据库名、字符集(如
utf8mb4)。
-
服务器端连接管理器处理
MySQL服务器的**连接管理器(Connection Manager)**负责监听并处理连接请求,核心工作包括:- 身份验证:验证用户名、密码的正确性(密码存储在
mysql.user表中,采用加密方式保存,如mysql_native_password); - 权限校验:验证该用户是否拥有目标数据库/表的
SELECT权限; - 线程分配:验证通过后,从线程池中分配一个线程处理该客户端的后续请求(避免频繁创建/销毁线程的开销);
- 连接维护:通过
wait_timeout(非交互式连接)、interactive_timeout(交互式连接)参数控制空闲连接的超时断开时间。
- 身份验证:验证用户名、密码的正确性(密码存储在
-
连接成功后的交互模式
连接建立后,客户端与服务器进入请求-响应模式,客户端可发送SQL查询语句,服务器端等待接收并处理。
阶段2:查询缓存(Query Cache,可选)
注意:MySQL 5.7及以下版本支持查询缓存,MySQL 8.0已彻底移除该功能。移除原因:缓存失效频率高(表数据修改会清空该表所有缓存)、维护成本高,多数业务场景下性能收益有限。
-
缓存的存储逻辑
查询缓存以完整的SQL语句字符串为Key,以查询结果集为Value,存储在内存中(由query_cache_size参数控制缓存大小)。- 只有完全相同的SQL语句(包括空格、大小写,除非开启
lower_case_table_names参数)才会命中缓存; - 包含动态函数(如
NOW()、RAND())、用户变量的SQL语句,不会被缓存(结果会随时间/场景变化)。
- 只有完全相同的SQL语句(包括空格、大小写,除非开启
-
缓存命中与未命中的处理
- 缓存命中:若SQL语句的Key存在于缓存中,且对应表数据未发生修改,服务器直接返回缓存结果,跳过后续解析、优化、执行步骤,这是最快的查询路径;
- 缓存未命中/无缓存:若SQL不在缓存中,或缓存因表数据修改失效,则进入SQL解析阶段。
阶段3:SQL解析与预处理(生成合法的语法树)
当查询缓存未命中时,MySQL需要对SQL语句进行语法校验和语义校验,将原始字符串转换为结构化的抽象语法树(AST)。
3.1 SQL解析器(Parser):语法校验与AST生成
MySQL的SQL解析器基于**BNF范式(巴科斯范式)**的语法规则工作,核心任务分为两步:
- 词法分析:将原始SQL字符串拆分为一个个独立的“单词(Token)”。例如
SELECT id, name FROM user WHERE age > 18会被拆分为SELECT、id、,、name、FROM、user、WHERE、age、>、18等Token。 - 语法分析:按照MySQL的SQL语法规则,校验Token的排列顺序是否合法。例如缺少
FROM子句、WHERE条件语法错误等,都会触发语法错误(报错You have an error in your SQL syntax),并终止查询流程。 - 生成AST:语法校验通过后,解析器将Token组合成抽象语法树(AST),AST以树形结构描述了SQL的逻辑(如查询的表、字段、过滤条件、排序方式等)。
3.2 预处理模块(Preprocessor):语义校验与AST优化
语法树仅保证SQL的语法合法,预处理模块负责语义校验和初步AST优化,核心任务包括:
- 语义校验:
- 验证查询的表、字段是否存在(如
SELECT non_exist_col FROM user会报错); - 验证用户是否拥有目标表的查询权限;
- 解析表/字段别名(如
SELECT id AS user_id FROM user,关联user_id与id); - 函数合法性校验(如
COUNT(1)合法,COUNT(invalid_arg)报错)。
- 验证查询的表、字段是否存在(如
- AST优化:
- 常量折叠:将
WHERE age > 10 + 8优化为WHERE age > 18,减少执行阶段的计算量; - 移除无效条件:将
WHERE 1=1直接移除,WHERE 1=0直接返回空结果集。
- 常量折叠:将
预处理通过后,优化后的AST会被传递给查询优化器。
阶段4:查询优化器(Optimizer):生成最优执行计划
MySQL是基于成本的优化器(CBO,Cost-Based Optimizer),其核心目标是:在多种可行的执行方案中,计算每种方案的执行成本(CPU开销、IO开销、内存开销等),选择成本最低的方案作为执行计划(Execution Plan)。
实操工具:可通过
EXPLAIN或EXPLAIN ANALYZE命令查看执行计划,这是SQL优化的核心工具。
4.1 优化器的核心输入
- 预处理后的AST;
- 表的统计信息(存储在
information_schema中,包括表行数、索引分布、数据分布等); - 索引信息(主键索引、辅助索引、联合索引等);
- 服务器参数配置(如
join_buffer_size、sort_buffer_size)。
4.2 优化器的核心优化动作
对于同一SQL,可能存在多种执行方案,优化器会通过以下动作选择最优方案:
- JOIN优化
- 驱动表选择:优先选择“小表”作为驱动表,减少关联次数(如
A JOIN B,若A是小表,则以A为驱动表); - 关联算法选择:支持
Nested Loop Join(适合小表关联)、Hash Join(MySQL 8.0支持,适合大表关联)、Merge Join(适合有序表关联)。
- 驱动表选择:优先选择“小表”作为驱动表,减少关联次数(如
- 索引优化
- 最优索引选择:基于索引的选择性(选择性=唯一值数量/总行数,值越高过滤效果越好)、索引覆盖(查询字段全部包含在索引中,避免回表)等因素选择索引;
- 索引下推(ICP):将部分过滤条件下推到存储引擎层,在索引扫描时直接过滤数据,减少回表次数。
- 查询重写
- 子查询优化:将相关子查询转换为JOIN查询(如
SELECT * FROM t1 WHERE id IN (SELECT id FROM t2)转换为t1 JOIN t2 ON t1.id=t2.id); - DISTINCT与GROUP BY转换:部分场景下
DISTINCT可通过GROUP BY优化,减少数据扫描; - 外连接转内连接:若外连接的
WHERE条件过滤了NULL值,自动转换为内连接,降低执行成本。
- 子查询优化:将相关子查询转换为JOIN查询(如
- 排序/分页优化
- 索引排序:若
ORDER BY字段包含在索引中,直接使用索引的有序性,避免Filesort(文件排序); - 分页优化:针对
LIMIT large_offset, small_rows(如LIMIT 10000, 10),优化器会通过索引定位到起始位置,减少无效数据扫描。
- 索引排序:若
4.3 输出执行计划
优化器完成成本计算后,会生成一份详细的执行计划,包含以下核心信息:
- 表的访问顺序;
- 表的访问方式(全表扫描
ALL、索引扫描ref/range/const等); - 使用的索引名称;
- 排序、分页、去重的实现方式;
- 多表关联的算法和顺序。
阶段5:执行器(Executor):调用存储引擎API执行计划
执行器是服务器层与存储引擎层的桥梁,其核心任务是:按照执行计划的步骤,调用对应存储引擎的API,完成数据的读取和处理,并生成结果集。
5.1 执行前的准备
- 表打开请求:执行器向存储引擎发送表打开请求,存储引擎会检查表是否已缓存(表缓存),未缓存则从磁盘加载表结构信息;
- 权限二次校验:防止连接建立后用户权限被修改,确保执行计划的权限合法。
5.2 按执行计划分步执行
执行器按照执行计划的树形结构,从最底层的表开始,分步执行以下操作:
- 数据读取
- 若执行计划要求使用索引,调用存储引擎的
index_readAPI,通过B+树索引定位数据行; - 若执行计划要求全表扫描,调用存储引擎的
table_scanAPI,逐行扫描表数据; - 存储引擎优先从**缓冲池(Buffer Pool)**读取数据(内存读取),缓冲池无数据则从磁盘读取,并将数据缓存到缓冲池(供后续查询复用)。
- 若执行计划要求使用索引,调用存储引擎的
- 数据过滤
- 对存储引擎返回的数据,按照
WHERE条件过滤(若已通过索引下推过滤,则仅过滤剩余条件); - 多表关联时,按照关联顺序将前一个表的结果与后一个表关联,再过滤。
- 对存储引擎返回的数据,按照
- 数据处理
- 排序:需要排序时,先将数据存入
sort_buffer,完成排序后输出;若数据量超过sort_buffer_size,则使用磁盘临时文件排序(Filesort); - 分组/聚合:按照
GROUP BY字段分组,执行SUM()/COUNT()/AVG()等聚合函数计算,若有HAVING条件则过滤分组结果; - 分页:按照
LIMIT条件截取结果行,生成最终结果集。
- 排序:需要排序时,先将数据存入
5.3 异常处理
执行过程中若出现错误(如锁等待超时、数据损坏),执行器会终止执行,回滚事务(针对事务查询),并返回错误信息给客户端。
阶段6:存储引擎层:数据的实际存储与读取
存储引擎层负责数据的物理存储和读取,不同存储引擎的实现差异较大,这里以InnoDB(MySQL默认存储引擎)为例,拆解其核心工作机制。
6.1 InnoDB的核心组件
- 缓冲池(Buffer Pool):InnoDB的内存缓存核心,存储数据页(16KB)和索引页,优先从缓冲池读取数据,减少磁盘IO;
- B+树索引:聚簇索引(主键索引)的叶子节点存储完整数据行,辅助索引的叶子节点存储主键值,通过主键值回表查询完整数据;
- 重做日志(Redo Log)/回滚日志(Undo Log):保证事务的原子性、一致性和持久性;
- 锁机制:支持行锁、间隙锁、Next-Key锁,保证并发查询的数据一致性。
6.2 InnoDB的数据读取流程
以辅助索引查询为例,数据读取流程如下:
- 执行器调用
index_readAPI,传入索引名称和查询条件; - InnoDB从缓冲池读取辅助索引的根节点,逐层向下查找,定位到满足条件的索引页;
- 在索引页中,通过页目录快速找到索引项,获取对应的主键值;
- 根据主键值,从聚簇索引中定位到数据页,读取完整数据行;
- 将数据行返回给执行器,并将读取的索引页和数据页缓存到缓冲池。
阶段7:结果集整理与返回
执行器完成数据处理后,会对结果集进行最后的整理,然后返回给客户端:
- 结果集格式化:按照客户端指定的字符集、数据格式,对结果集进行格式化;
- 分批返回:若结果集过大,执行器会分批发送数据(避免一次性占用过多内存),客户端通过流式方式接收;
- 资源释放:释放本次查询占用的临时资源(如临时表、
sort_buffer),存储引擎释放表锁(若有); - 连接维护:结果返回完成后,连接进入空闲状态,等待下一次请求(直到超时断开)。
阶段8:客户端接收并展示结果
客户端接收到服务器返回的结果集后,按照自身的展示逻辑(如命令行的表格格式、GUI工具的可视化表格),将结果呈现给用户,至此,整个MySQL查询执行流程结束。
三、影响查询执行效率的核心因素
理解查询执行流程的最终目的是优化查询效率,以下是影响MySQL查询性能的核心因素:
- 索引设计:合理的索引是提升查询效率的核心,需避免冗余索引、无效索引,优先创建联合索引、覆盖索引;
- 统计信息准确性:优化器依赖表的统计信息选择执行计划,大表批量插入/删除后,需执行
ANALYZE TABLE更新统计信息; - 缓冲池配置:
innodb_buffer_pool_size是InnoDB最重要的参数,建议设置为物理内存的50%~70%,减少磁盘IO; - SQL写法:避免
SELECT *、避免在WHERE条件中使用函数/表达式(如WHERE DATE(create_time) = '2024-01-01'会导致索引失效)、优化大偏移量分页查询; - 服务器参数:合理配置
join_buffer_size(关联缓冲区)、sort_buffer_size(排序缓冲区)、read_buffer_size(顺序读取缓冲区)等参数; - 存储引擎选择:InnoDB支持事务、行锁、缓冲池,适合大部分业务场景;MyISAM不支持事务,适合只读场景。
四、典型案例演示:一条SQL的完整执行过程
为了让读者更直观理解,以一条实际SQL为例,演示其完整执行流程。
示例场景
- 表结构:
user表(id为主键,age为辅助索引,包含name、create_time字段); - SQL语句:
SELECT id, name FROM user WHERE age = 25 ORDER BY id LIMIT 10;
完整执行流程
- 连接建立:客户端与MySQL服务器建立TCP连接,通过身份验证和权限校验;
- 查询缓存:MySQL 8.0无缓存,直接进入解析阶段;
- 解析与预处理:解析器生成AST,预处理模块验证
id、name、age字段存在,无语法和语义错误; - 优化器生成执行计划:
- 分析可选方案:使用
age辅助索引(过滤age=25)或全表扫描; - 成本计算:
age索引选择性高,过滤后数据量少,选择age辅助索引; - 优化:
ORDER BY id可利用聚簇索引的有序性,无需额外排序;LIMIT 10提前终止扫描; - 生成执行计划:
age索引扫描 → 回表取id、name→ 按id排序 → 截取前10行;
- 分析可选方案:使用
- 执行器执行:
- 调用InnoDB的
index_readAPI,通过age索引定位到age=25的索引项; - 获取主键值,通过聚簇索引回表读取
id、name字段; - 收集数据,无需额外排序,截取前10行生成结果集;
- 调用InnoDB的
- 结果返回:执行器将结果集格式化后,分批返回给客户端;
- 客户端展示:客户端接收并展示结果。
执行计划验证
通过EXPLAIN命令查看执行计划:
EXPLAIN SELECT id, name FROM user WHERE age = 25 ORDER BY id LIMIT 10;
执行结果的核心字段说明:
type:ref(表示使用辅助索引扫描);key:age(表示使用age索引);Extra:Using index condition(表示启用索引下推),无Using filesort(表示未使用文件排序)。
五、总结
- MySQL查询执行流程分为服务器层和存储引擎层,其中查询优化器是核心,负责生成最优执行计划,执行器是连接两层的桥梁;
- 完整流程可概括为8步:连接建立→查询缓存→解析预处理→优化器生成执行计划→执行器执行→存储引擎读取数据→结果整理→返回客户端;
- 影响查询效率的关键因素包括索引设计、统计信息准确性、缓冲池配置、SQL写法等,通过
EXPLAIN命令可查看执行计划,针对性优化SQL; - InnoDB的缓冲池、B+树索引、锁机制是支撑高效查询的核心组件,也是MySQL性能优化的重点。
更多推荐


所有评论(0)