MySQL 查询执行的流程

1. 查询执行路径

在这里插入图片描述
上图为 MySQL 查询执行的流程概览,其主要流程为以下几步:

  1. 客户端将查询请求发送到服务端
  2. 服务端首先检查查询缓存是否开启,如果开启并且当前查询命中缓存就从缓存中返回结果,否则进行下一步
  3. 服务端解析 SQL 语句并进行预处理,再由查询优化器生成对应的执行计划
  4. 执行引擎根据执行计划调用存储引擎 API 执行查询
  5. 服务端将结果发送回客户端

2. 查询执行步骤详解

2.1 客户端发起查询请求

  1. 首先需要清楚 MySQL 客户端与服务器端的通信协议是 半双工的,就是在任何时刻要么由客户端向服务器端发送数据,要么由服务器端向客户端发送数据,这两个动作不能同时发生。这种协议让 MySQL 通信简单快速,但是缺点在于一旦一端开始发送数据另一端要接收完所有信息才能响应,这样就没法进行流量控制

  2. 客户端发起查询时首先用一个单独的数据包将查询发送到服务器,因为半双工机制一旦客户端发送了查询,剩下的就是等待结果

    如果一个查询过大,有时会出现"MySQL server has gone away"的错误,原因可能就是传送的数据太大导致连接断开了,可以通过命令SHOW VARIABLES LIKE "%max_allowed_packet%" 查看服务器所允许传送的最大数据,该值可在my.cnf文件(WIN 环境 my.ini)里配置

  3. 服务器响应的数据通常很多,由多个数据包组成。服务器发送响应的时候客户端必须接收完整的结果集,不能只提取几行数据后要求服务器停止发送剩下的数据,所以使用LIMIT来限制所需要的数据行数有时是必要的

    MySQL 需要所有数据都已经发送给客户端之后才能释放这条查询所占用的资源,所以客户端接收全部结果并缓存可以减少服务器端的压力,让服务器端能够早点释放资源。与之相对的,则是内存压力转移到了客户端,由客户端负责缓存数据,并从内存中按需取用

查询状态

每个 MySQL 连接在任意时间点都有一个状态来标识正在进行的事情,可以使用 SHOW FULL PROCESSLIST 命令来查看哪些线程正在运行,及其查询状态,Command 列显示了状态

mysql> SHOW FULL PROCESSLIST;
+----+-----------------+-----------+------+---------+-------+------------------------+-----------------------+
| Id | User            | Host      | db   | Command | Time  | State                  | Info                  |
+----+-----------------+-----------+------+---------+-------+------------------------+-----------------------+
|  4 | event_scheduler | localhost | NULL | Daemon  | 86599 | Waiting on empty queue | NULL                  |
|  9 | root            | localhost | NULL | Query   |     0 | starting               | SHOW FULL PROCESSLIST |
+----+-----------------+-----------+------+---------+-------+------------------------+-----------------------+

MySQL 服务端连接状态 说明
Sleep 线程正在等待客户端,以向它发送一个新请求
Query 线程正在执行查询或往客户端发送数据
Locked 该查询所需的资源被其它查询锁定,通常表示在等待表锁
Analyzing and statistics 线程正在收集存储引擎的统计信息,并生成查询的执行计划
Copying to tmp table [on disk] 正在执行查询,并将结果集都复制到一张临时表中。如果有on disk表示临时结果集合大于tmp_table_size,线程把临时表从存储器内部放到磁盘上
Sending data 线程正在为SELECT语句处理行,同时正在向客户端发送数据
Sorting for group 线程正在进行分类,以满足 GROUP BY要求
Sorting for order 线程正在进行分类,以满足 ORDER BY要求

2.2 查询缓存

在解析 SQL 语句之前,如果开启了查询缓存,那么 MySQL 会检查查询缓存,进行大小写敏感的哈希查找。查找规则比较严格,即使查询和缓存中的查询只有一个字节的差异,也表示不匹配,查询就会进入下一阶段

MySQL 查询缓存保留了查询返回给客户端的完整结果,当缓存命中的时候服务器会先检查权限,检查通过后直接返回保存的结果,跳过解析、优化和执行步骤。查询缓存会跟踪查询使用过的每个表,如果这些表数据发生了改变,那么和这个表相关的缓存数据就失效了

MySQL 判定缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值进行引用。这个哈希值包含了查询本身、要查询的数据库、客户端协议版本等一些可能会影响返回结果的信息,以便正确地命中缓存。需注意,当查询语句中有一些不确定的数据时,则不会被缓存,例如包含函数 NOW()、CURRENT_DATE()等。另外查询缓存是在完整的 SELECT 语句基础上的,只在刚刚收到 SQL 语句的时候检查,所以子查询和存储过程都无法使用查询缓存

2.3 SQL 解析与预处理

MySQL 根据关键字对 SQL 语句进行解析,将查询分解成一个个标识,从而生成一棵对应的解析树

  1. 解析器负责保证查询中的标识都是有效的,它将使用 MySQL 语法规则对解析树进行验证和解析,例如验证是否使用错误的关键字、使用关键字的顺序是否正确,或者字符串上面的引号有没有闭合等
  2. 预处理器则根据一些MySQL规则进一步检查解析树是否合法,例如检查数据表和数据列是否存在,还会解析字段名称和别名,看看它们是否有歧义。最后,预处理器会检查权限

2.4 查询优化器优化

查询优化器负责把解析树变成执行计划,一个查询通常可以有很多种执行方式,优化器的任务就是找到最好的方式

MySQL 的查询优化器基于成本决策,它将尝试预测一个查询使用某种执行计划的成本,并选择其中成本最小的一个,可以通过命令SHOW STATUS LIKE "Last_query_cost"得知当前会话中 MySQL 计算的当前查询的成本

MySQL的查询优化为了生成一个最优的执行的计划使用了很多优化策略,它们可以分为两种,静态优化和动态优化

  • 静态优化
    直接对解析树进行分析,并完成优化。例如优化器可以通过一些简单的代数变换将 where 条件转换成另一种等价形式。静态优化不依赖于特别的数值,如 where 条件中带入的一些常数等。静态优化在第一次完成后就一直有效,即使使用不同的参数重复查询也不会变化,可以认为是一种编译时优化
  • 动态优化
    动态优化与查询的上下文有关,也可能和很多其他因素有关,例如 where 条件中的取值、索引中条目对应的数据行数等,这些每次查询的时候都需要重新评估,可以认为是运行时优化

以下是一些MySQL能够处理的优化类型:

优化类型 说明
重新定义关联表的顺序 数据表的关联并不总是按照在查询中指定的顺序进行
将外连接转化成内连接 并不是所有的outer join语句都必须以外连接的方式执行。诸多因素,例如where条件、库表结构都可能会让外连接等价于一个内连接。MySQL能够识别这点并重写查询,让其可以调整关联顺序,以便适应其它的优化,比如排序
使用等价变换规则 MySQL可以使用一些等价变换来简化并规范表达式。它可以合并和减少一些比较,还可以移除一些恒成立和一些恒不成立的判断。例如:(5=5 and a>5)将被改写为a>5。类似的,如果有(a< b and b=c)and a=5,则会被改写为 b>5 and b=c and a=5
优化count()、min()和max() 索引和列是否为空通常可以帮助MySQL优化这类表达式。例如,要找到一列的最小值,只需要查询对应B-tree索引最左端的记录,MySQL可以直接获取索引的第一行记录。在优化器生成执行计划的时候就可以利用这一点,在B-tree索引中,优化器会讲这个表达式最为一个常数对待。类似的,如果要查找一个最大值,也只需要读取B-tree索引的最后一个记录。如果MySQL使用了这种类型的优化,那么在explain中就可以看到“select tables optimized away”。从字面意思可以看出,它表示优化器已经从执行计划中移除了该表,并以一个常数取而代之
预估并转化为常数表达式 MySQL 检测到一个表达式可以转化为常数的时候,就会一直把该表达式当作常数进行优化处理,数学表达式就是一个典型的例子
覆盖索引扫描 当索引中的列包含所有查询中需要使用的列的时候,MySQL就可以使用索引返回需要的数据,而无需查询对应的数据行
子查询优化 MySQL在某些情况下可以将子查询转换成一种效率更高的形式,从而减少多个查询多次对数据进行访问
提前终止查询 在发现已经满足查询需求的时候,MySQL总是能够立即终止查询,一个典型的例子就是当使用了limit子句的时候。除此之外,MySQL还有几种情况也会提前终止查询,例如发现了一个不成立的条件,这时MySQL可以立即返回一个空结果
等值传播 如果两个列都值通过等式关联,MySQL 能够将其中一个列的 where 条件传递到另一列上
列表in()的比较 MySQL将in()列表中的数据先进行排序,然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个o(log n)复杂度的操作,等价转换成or的查询的复杂度为o(n),对于in()列表中有大量取值的时候,MySQL的处理速度会更快

2.5 查询执行引擎

在查询优化阶段,MySQL 将生成查询对应的执行计划,MySQL 的查询执行引擎则根据这个执行计划给出的指令逐步执行来完成整个查询。在这个过程中,有大量的操作需要通过调用存储引擎实现的被称为“handler API”的接口来完成

MySQL在优化阶段就为每个表创建了一个 handler 实例,优化器根据这些实例的接口可以获取表的相关信息,包括表的所有列名、索引统计信息等

2.6 结果返回

查询执行的最后一个阶段是将结果返回给客户端,即使查询不需要返回结果给客户端,MySQL仍然会返回这个查询的一些信息,例如查询影响到的行数。如果查询可以被缓存,那么MySQL在这个阶段会将结果存放到查询缓存中

MySQL 将结果返回客户端是一个增量、逐步返回的过程,例如在关联表操作时,一旦服务器处理完最后一个关联表,开始生成第一条结果时,MySQL 就可以开始向客户端逐步返回结果集了。这样处理有两个优点:

  1. 服务器无需存储太多的结果,也就不会因为要返回太多的结果而消耗太多的内存
  2. 这样的处理让 MySQL 客户端可以在第一时间获得返回的结果

猜你喜欢

转载自blog.csdn.net/weixin_45505313/article/details/106540525
今日推荐