你是否每天都在写 SQL,却从未想过它在 MySQL 内部是如何一步步执行的?
今天我们就通过这张经典的 MySQL 执行流程图,带你拆解一条 SQL 从客户端发送到结果返回的完整过程,搞懂这个过程,你就能轻松理解 SQL 优化、事务原理、锁机制等核心知识点。
一、先搞懂 MySQL 的整体架构
MySQL 的架构可以清晰地分为两层:Server 层和存储引擎层,这也是 MySQL 最核心的设计思想 ——插件式存储引擎。
- Server 层:包含连接器、查询缓存、分析器、优化器、执行器等核心组件,负责处理所有跨引擎的通用逻辑,比如 SQL 解析、优化、权限检查、结果缓存等。
- 存储引擎层:负责数据的存储和读取,提供读写接口。常见的存储引擎有 InnoDB(MySQL 5.5 + 默认)、MyISAM、Memory 等,不同的存储引擎有不同的特性,比如 InnoDB 支持事务和行锁,MyISAM 不支持。
简单来说:Server 层负责 “怎么干”,存储引擎层负责 “真正干”。
二、一条 SQL 的完整执行流程
我们以最常见的查询语句为例:
SELECT user_name, user_salary FROM t_user WHERE user_id = 100;看看它在 MySQL 内部经历了怎样的旅程。
第一步:建立连接 —— 连接器
当你在客户端点击 “连接” 按钮时,第一个打交道的就是连接器。
连接器的核心工作有三件:
- 建立 TCP 连接:客户端与 MySQL 服务器通过 3 次握手建立网络连接。
- 身份验证:验证你输入的用户名和密码是否正确。如果错误,会返回经典的
Access denied for user错误,连接直接终止。 - 权限管理:验证通过后,连接器会从权限表中查询该用户拥有的所有权限。注意:连接建立后,即使管理员修改了该用户的权限,也不会影响当前连接,必须等下次重新连接才会生效。
长连接 vs 短连接
图中特别提到了长连接和短连接的区别,这也是生产环境中非常重要的知识点:
- 长连接:客户端有需求时一直使用同一个连接。
- 优点:只需要建立一次连接,减少连接建立的开销。
- 缺点:长时间连接会占用大量内存,因为 MySQL 在执行过程中使用的临时内存会保存在连接对象中,直到连接断开才会释放。
- 短连接:每次执行完很少的几次查询就断开连接,下次查询再重新建立。
- 优点:不会长时间占用内存。
- 缺点:频繁建立连接会增加服务器开销。
最佳实践:使用长连接,但定期断开空闲时间过长的连接;MySQL 5.7 及以上版本可以使用mysql_reset_connection命令重置连接,释放内存而不需要断开重连。
第二步:查询缓存(MySQL 8.0 已彻底移除)
连接建立后,MySQL 拿到 SQL 语句,首先会去查询缓存中查找是否有对应的结果。
- 缓存的 key 是完整的 SQL 语句,value 是查询结果集。
- 如果在缓存中找到了完全匹配的 SQL,就会直接将结果返回给客户端,跳过后面所有步骤,速度非常快。
- 如果缓存中没有找到,就会继续执行后面的流程,执行完成后,会将结果存入查询缓存。
为什么 MySQL 8.0 要移除查询缓存?
查询缓存看起来很美好,但实际上它的弊远大于利:
- 缓存失效太频繁:只要对一个表执行了任何更新操作(INSERT、UPDATE、DELETE),这个表的所有查询缓存都会被清空。
- 对于写多读少的业务场景,缓存几乎没有任何作用,反而会增加缓存维护的开销。
因此,MySQL 从 8.0 版本开始直接删除了查询缓存功能,以后再也不用纠结要不要开启缓存了。
第三步:解析 SQL—— 分析器
如果缓存没有命中,MySQL 就会开始真正处理这条 SQL,第一个环节就是分析器。
分析器的工作分为两步:
- 词法分析:从 SQL 语句中提取关键字。比如上面的例子,分析器会识别出
SELECT是查询关键字,t_user是表名,user_name、user_salary是列名,user_id = 100是查询条件。 - 语法分析:根据 MySQL 的语法规则,判断 SQL 语句是否合法。如果你的 SQL 写错了,比如把
FROM写成了FORM,少了分号,或者关键字用错了,分析器会返回经典的You have an error in your SQL syntax错误,错误信息通常会提示你在哪个位置附近出错。
分析器执行完成后,会生成一棵语法树,供后面的优化器使用。
第四步:生成执行计划 —— 优化器
拿到语法树后,MySQL 还不能直接执行,它需要通过优化器生成一个最优的执行方案。
优化器的核心工作就是:在多个可能的执行方案中,选择一个效率最高的。
比如:
- 当表中有多个索引时,优化器会判断使用哪个索引查询速度最快。
- 当进行多表连接查询时,优化器会决定先查询哪个表,再连接哪个表(通常是先查小表,再查大表)。
举个例子:
SELECT * FROM t_user WHERE name = '张三' AND age = 25;如果name列和age列都有索引,优化器会比较两个索引的过滤性:如果叫 “张三” 的人只有 1 个,而 25 岁的人有 1000 个,那么优化器会选择使用name索引,因为它能更快地定位到目标数据。
需要注意的是,优化器的选择并不总是正确的,有时候它会选错索引,这时候就需要我们手动干预,使用FORCE INDEX强制使用某个索引。
第五步:执行 SQL—— 执行器
优化器生成执行计划后,就交给执行器来真正执行 SQL 语句。
执行器的执行流程:
- 权限检查:再次检查用户是否有执行该操作的权限。比如你只有查询权限,却执行了删除操作,执行器会返回权限不足的错误。
为什么这里还要再检查一次权限?因为有些权限是在运行时才能确定的,比如视图的权限。
- 调用存储引擎接口:执行器本身不读写数据,它会根据执行计划,调用存储引擎提供的接口来读取或写入数据。
- 逐行处理数据:执行器会逐行读取存储引擎返回的数据,判断是否满足查询条件,如果满足,就将该行加入结果集。
- 返回结果:将收集到的结果集返回给客户端。
还是以上面的查询为例:执行器会调用 InnoDB 的接口,从user_id索引中找到user_id = 100的那一行,然后读取该行的user_name和user_salary字段,返回给客户端。
如果是没有索引的全表扫描,执行器会调用存储引擎的接口,逐行读取表中的所有数据,然后判断是否满足条件,直到扫描完整个表。这就是为什么全表扫描速度很慢的原因。
第六步:存储数据 —— 存储引擎
存储引擎是真正与磁盘打交道的组件,它负责将数据存储到磁盘,以及从磁盘中读取数据。
以最常用的 InnoDB 存储引擎为例:
- 当执行读操作时,InnoDB 会先从缓冲池(Buffer Pool)中查找数据,如果找到了就直接返回;如果没有找到,就从磁盘中读取数据,加载到缓冲池,然后再返回给执行器。
- 当执行写操作时,InnoDB 会先写Redo Log(重做日志)(保证持久性),然后修改缓冲池中的数据,最后在合适的时机将缓冲池中的数据异步刷到磁盘。
不同的存储引擎有不同的实现方式,比如 MyISAM 不支持事务和行锁,它的读写性能在某些场景下比 InnoDB 好,但因为不支持事务,现在已经很少使用了。
三、完整流程总结
我们再把上面的步骤串起来,回顾一下一条 SQL 的完整执行过程:
- 客户端通过 TCP 连接 MySQL 服务器,连接器验证身份并获取权限。
- (MySQL 5.7 及以下)查询缓存,命中则直接返回结果。
- 分析器对 SQL 进行词法分析和语法分析,生成语法树。
- 优化器根据语法树生成最优的执行计划。
- 执行器根据执行计划,调用存储引擎的接口执行 SQL。
- 存储引擎从磁盘或缓冲池中读取数据,返回给执行器。
- 执行器将结果集返回给客户端。
四、了解执行流程有什么用?
搞懂 MySQL 的执行流程,你就能:
- 更好地优化 SQL:知道索引是在哪个环节生效的,为什么全表扫描慢,为什么优化器会选错索引。
- 更快地排查问题:当 SQL 执行慢时,能快速定位是连接问题、缓存问题、分析器问题、优化器问题还是存储引擎问题。
- 深入理解 MySQL 核心原理:事务、锁、MVCC 等机制都是基于这个执行流程实现的。
希望这篇文章能帮你彻底搞懂 MySQL 的执行流程,以后再写 SQL 的时候,就能做到 “知其然,更知其所以然” 了。