mysql 查询语句可以分为 server 层和存储引擎层。而 server 层包括连接器、查询缓存、解析器、预处理器、优化器、执行器等,
最后 server 层再通过 api 接口形式调用对应的存储引擎层提供的接口来执行增删改查操作。
如下图所示:
1、介绍
关于sql语句在执行过程中,可能会涉及到以下的组件来进行相互的调用。
1.1、组件介绍
组件及其作用,可参考:
1.2、sql执行顺序
在 sql 中,select语句的语法结构看似是按照关键字书写顺序执行的(如select ...
from ...
where ...
),但实际执行顺序与语法顺序并不完全一致。
如下图所示:
注意:
在包含子查询的 sql 中,子查询本身也是一个完整的select语句,其内部仍然遵循上述执行顺序。外部查询的执行顺序会以子查询的最终结果为输入继续执行。
为了更好地理解这个顺序,我们可以从“数据流”的角度思考:
from
:首先确定从哪个表或视图中读取数据。where
:在获取原始数据后,先进行行级过滤,减少后续处理的数据量。group by
:将过滤后的数据按指定字段分组,为聚合操作做准备。having
:在分组后,进一步过滤不满足条件的组。select
:确定最终返回的字段(包括常量、表达式、聚合函数等)。order by
:对最终结果进行排序。limit
:限制返回的记录数量。
如下图所示:
2、执行流程
一个完整的执行流程包括以下组件,连接器、缓存、解析器、优化器、执行器和存储引擎组成。
如下图所示:
2.1. 连接与认证
mysql 服务端和客户端的通信方式采用的是半双工协议。
客户端连接:客户端通过协议(如 tcp/ip、socket)与 mysql 服务端建立连接。
认证:服务端验证用户身份(用户名、密码、权限)。如果验证失败,连接终止。
权限检查:确认用户是否有权限执行当前查询(如 select 权限)。
常见的通信方式主要可以分为三种:单工,半双工,全双工。
单工:
通信的时候,数据只能单向传输。比如说遥控器,我们只能用遥控器来控制电视机,而不能用电视机来控制遥控器。
半双工:
通信的时候,数据可以双向传输,但是同一时间只能有一台服务器在发送数据,当 a 给 b 发送数据的时候,那么 b 就不能给 a
发送数据,必须等到 a 发送结束之后,b 才能给 a 发送数据。比如说对讲机。
全双工:
通信的时候,数据可以双向传输,并且可以同时传输。比如说我们打电话或者用通信软件进行语音和视频通话等。
半双工协议让 mysql 通信简单快速,但是也在一定程度上限制了 mysql 的性能,因为一旦从一端开始发送数据,另一端必须要接收完全部数据才能做出响应。
所以当批量插入的时候尽量拆分成多次插入而不要一次插入太大数据,同样的查询语句最好也带上 limit 限制条数,避免一次返回过多数据。
mysql 单次传输数据包的大小可以通过变量 max_allowed_packet 控制,默认大小为 64mb(5.7 版本默认只有 4mb)。
执行以下语句查看 max_allowed_packet 变量大小:
2.2. 查询缓存
缓存命中检查:
如果开启了查询缓存(query cache),mysql 会直接检查是否有完全相同的查询结果缓存。
命中:直接返回缓存结果。
未命中:进入后续流程,并可能将结果写入缓存(取决于配置)。
注意:mysql 8.0 已移除查询缓存功能。
移除原因:
因为 mysql 的缓存使用条件非常苛刻,是通过一个大小写敏感的哈希值去匹配的,这样就是说一条查询语句哪怕只是有一个空格不一致,都会导致无法使用缓存。而且一旦表里面有一行数据变动了,那么关于这种表的所有缓存都会失效,所以一般我们都是不建议使用缓存。
在 mysql 8.0 版本之前缓存也是默认关闭的,可以通过变量 query_cache_type 进行控制。
2.3. 语法解析(parser)
词法分析:将 sql 语句拆分为 token(如关键字、标识符、操作符等)。
语法分析:根据 sql 语法树规则,检查语句合法性(如select * from table
是否符合语法)。
生成抽象语法树(ast):将 sql 转换为数据库可理解的内部结构。
整个sql语句会被分割成:select,name,from,table,where,id,=,1这几个字符。并且能识别出关键字和非关键字,然后根据 sql 语句生成一个数据结构,也叫做解析树。
如下图所示:
2.4、执行sql
如下图所示:
1. 预处理(preprocessor)
语义检查:
- 检查表、列是否存在。
- 验证权限(如用户是否有权限访问指定的表或列)。
- 替换别名、处理函数等。
生成逻辑查询计划:
- 将 ast 转换为逻辑执行计划(如select a from t1 where b = 5)。
2. 查询优化器(optimizer)
其目标是生成最优的执行计划(即最小资源消耗、最快响应时间)。
优化步骤:
选择访问路径:决定是否使用索引(如全表扫描 vs 索引扫描)。如果有多个索引,选择最合适的索引。
连接顺序优化(针对多表查询):决定表的连接顺序(如 a join b join c 的顺序)。使用动态规划或代价模型(cost model)计算最优顺序。
其他优化:优化子查询(如转换为 join)。优化排序(order by)和分组(group by)。常量传播、条件简化等。
输出执行计划:生成物理执行计划(如使用哪个索引、连接算法等)。
3. 执行器(executor)
执行查询计划:
- 调用存储引擎接口(如 innodb、myisam)获取数据。
- 根据执行计划逐层处理(如过滤、排序、分组)。
权限二次检查:
- 确保用户对访问的数据有权限。
数据处理:
- 读取数据行(通过全表扫描或索引扫描)。
- 应用 where 条件过滤。
- 对结果进行排序、分组、聚合等操作。
2.5. 存储引擎交互
存储引擎接口:
- mysql 通过统一的接口(handler api)与存储引擎交互。
innodb 的处理:
- 通过 b+ 树索引定位数据。
- 处理事务(如锁机制、mvcc)。
- 返回符合条件的记录。
其他引擎:
- 如 myisam 的处理方式不同(无事务支持)。
2.6. 返回结果
结果集组装:
- 将处理后的数据按用户指定的格式(如 select 字段)组装。
分页处理:
- 若涉及 limit 或分页,截取对应的数据。
返回客户端:
- 一次性返回(小结果集)。
- 流式返回(大结果集,通过网络分批次传输)。
mysql 将查询结果返回是一个增量的逐步返回过程。
当处理完所有查询逻辑并开始执行查询并且生成第一条结果数据的时候,mysql 就可以开始逐步的向客户端传输数据了。这么做的好处是服务端无需存储太多结果,从而减少内存消耗。
3、示例流程(select 查询)
以select * from users where id = 1;
为例:
- 客户端连接数据库。
- 检查查询缓存(未命中)。
- 解析 sql 语法,生成 ast。
- 预处理:检查
users
表和id
列是否存在。 - 优化器选择
id
索引进行查找。 - 执行器调用 innodb 引擎,通过索引定位到
id=1
的记录。 - 返回结果给客户端。
4、常见问题与优化
- 慢查询:检查执行计划(使用
explain
),优化索引或调整查询逻辑。 - 全表扫描:添加合适的索引或优化查询条件。
- join 性能:调整连接顺序或使用索引。
- 缓存失效:频繁更新的表可能导致缓存命中率低(mysql 8.0 后不再依赖此)。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论