ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
[TOC] ***** MySQL执行一个查询的过程 ![](https://box.kancloud.cn/c7c369c5393181e9c74c68dd3149f154_714x532.png) 1.客户端发送一条查询给服务器。 2.服务器先检查查询缓存,如果命中了缓存,返回结果。 3.服务器端进行SQL解析、预处理,由优化器生成执行计划。 4.MySQL调用存储引擎的API来执行计划。 5.将结果返回给客户端。 ## 6.4.1 MySQL客户端/服务器通信协议 MySQL客户端和服务器之间的通信协议是“半双工”的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据。 一旦一端开始发生消息,另一端要接收完整个消息才能响应它。 客户端用一个单独的数据包将查询传给服务器。这也是为什么当查询的语句很长的时候,参数max_allowed_packet就特别重要了(如果查询太大,服务端会拒绝接收更多的数据并抛出相应错误。) 一般服务器响应给用户的数据通常很多,由多个数据包组成。当服务器开始响应客户端请求时,客户端必须完整地接收整个返回结果,而不能简单地只取前面几条结果,然后让服务器停止发送数据。 多数连接MySQL的库函数默认一般是获得全部结果集并缓存到内存中。MySQL通常需要等所有的数据都已经发送给客户端才能释放这条查询所占用的资源,所以接收全部结果并缓存通常可以减少服务器的压力,让查询能够早点结束、早点释放相应的资源。 但是如果需要返回一个很大的结果集的时候,这样做并不好,因为库函数会花很多时间和内存来存储所有的结果集。如果能够尽早开始处理这些结果集,就能大大减少内存的消耗,这种情况下可以不使用缓存来记录结果而是直接处理。这样做的缺点是,对于服务器来说,需要查询完成后才能释放资源,所以在和客户端交互的整个过程中,服务器的资源都是被这个查询所占用的(你可以使用SQL_BUFFER_RESULT,后面将再介绍这点。) ***** **查询状态** 对于一个MySQL连接,或者说一个线程,任何时刻都有一个状态,该状态表示了MySQL当前正在做什么。有很多种方式能查看当前的状态,最简单的是使用SHOW FULL PROCESSLIST命令(该命令返回结果中的Command列就表示当前的状态)。在一个查询的生命周期中,状态会变化很多次。MySQL官方手册中对这些状态值的含义有最权威的解释,下面将这些状态列出来,并做一个简单的解释。 Sleep 线程正在等待客户端发送新的请求。 Query 线程正在执行查询或者正在将结果发送给客户端。 Locked 在MySQL服务器层,该线程正在等待表锁。在存储引擎级别实现的锁,例如InnoDB的行锁,,并不会体现在线程状态中。对于MyISAM来说这是一个比较典型的状态,但在其他没有行锁的引擎中也经常会出现。 Analyzing and statistics 线程正在收集存储引擎的统计信息,并生成查询的执行计划。 Copying to tmp table [on disk] 线程正在执行查询,并且将其结果集都复制到一个临时表中,这种状态一般要么是在做GROUP BY操作,要么是文件排序操作,或者是UNION操作。如果这个状态后面还有“on disk”标记,那表示MySQL正在将一个内存临时表放到磁盘上。 The thread is 线程正在对结果集进行排序。 Sending data 这表示多种情况:线程可能在多个状态之间传送数据,或者在生成结 集,或者在向客户端返回数据 ## 6.4.2 查询缓存 在解析一个查询语句之前,如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据。这个检查是通过一个对大小写敏感的哈希查找实现的。 如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前MySQL会检查一次用户权限。 如果权限没有问题,MySQL会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行。 ## 6.4.3 查询优化处理 查询的生命周期的下一步是将一个SQL转换成一个执行计划,MySQL再依照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析SQL、预处理、优化SQL执行计划。这个过程中任何错误(例如语法错误)都可能终止查询。 **语法解析器和预处理** MySQL解析器将使用MySQL语法规则验证和解析查询,预处理器则根据一些MySQL规则进一步检查解析树是否合法,下一步预处理器会验证权限。 **查询优化器** 现在语法树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。 MySQL使用基于成本的优化器,它将尝试预测一个查询使用某种执行计划时的成本,并选择其中成本最小的一个。最初,成本的最小单位是随机读取一个4K数据页的成本,后来(成本计算公式)变得更加复杂,并且引入了一些“因子”来估算某些操作的代价,如当执行一次WHERE条件比较的成本。可以通过查询当前会话的Last_query_cost的值来得知MySQL计算的当前查询的成本 **![](https://box.kancloud.cn/0e836cf05c9cd21b6adf9d13d69109ac_685x226.png)** 这个结果表示MySQL的优化器认为大概需要做1040个数据页的随机查找才能完成上面的查询。这是根据一系列的统计信息计算得来的:每个表或者索引的页面个数、索引的基数(索引中不同值的数量)、索引和数据行的长度、索引分布情况。优化器在评估成本的时候并不考虑任何层面的缓存,它假设读取任何数据都需要一次磁盘I/O。 **有很多种原因会导致MySQL优化器选择错误的执行计划,如下所示:** * 统计信息不准确。MySQL依赖存储引擎提供的统计信息来评估成本,但是有的存储引擎提供的信息是准确的,有的偏差可能非常大。例如,InnoDB因为其MVCC的架构,并不能维护一个数据表的行数的精确统计信息。 * 执行计划中的成本估算不等同于实际执行的成本。所以即使统计信息精准,优化器给出的执行计划也可能不是最优的。例如有时候某个执行计划虽然需要读取更多的页面,但是它的成本却更小。因为如果这些页面都是顺序读或者这些页面都已经在内存中的话,那么它的访问成本将很小。MySQL层面并不知道哪些页面在内存中、哪些在磁盘上,所以查询实际执行过程中到底需要多少次物理I/O是无法得知的。 * MySQL的最优可能和你想的最优不一样。你可能希望执行时间尽可能的短,但是 MySQL只是基于其成本模型选择最优的执行计划,而有些时候这并不是最快的执行方式。所以,这里我们看到根据执行成本来选择执行计划并不是完美的模型。 * MySQL从不考虑其他并发执行的查询,这可能会影响到当前查询的速度。 * MySQL也并不是任何时候都是基于成本的优化。有时也会基于一些固定的规则,例如,如果存在全文搜索的MATCH()子句,则在存在全文索引的时候就使用全文索引。即使有时候使用别的索引和WHERE条件可以远比这种方式要快,MySQL也仍然会使用对应的全文索引。 * MySQL不会考虑不受其控制的操作的成本,例如执行存储过程或者用户自定义函数的成本。 * 后面我们还会看到,优化器有时候无法去估算所有可能的执行计划,所以它可能错过实际上最优的执行计划。 **MySQL的查询优化器优化策略可以简单地分为两种,一种是静态优化,一种是动态优化。** * 静态优化可以直接对解析树进行分析,并完成优化。例如,优化器可以通过一些简单的代数变换将WHERE条件转换成另一种等价形式。静态优化不依赖于特别的数值,如WHERE条件中带入的一些常数等。静态优化在第一次完成后就一直有效,即使使用不同的参数重复执行查询也不会发生变化。可以认为这是一种“编译时优化”。 * 动态优化则和查询的上下文有关,也可能和很多其他因素有关,例如WHERE条件中的取值、索引中条目对应的数据行数等。这需要在每次查询的时候都重新评估的时候都重新评估,可以认为这是“运行时优化”。 在执行语句和存储过程的时候,动态优化和静态优化的区别非常重要。MySQL对查询的静态优化只需要做一次,但对查询的动态优化则在每次执行时都需要重新评估。有时候甚至在查询的执行过程中也会重新优化。 **下面是一些MySQL能够处理的优化类型: 重新定义关联表的顺序** 数据表的关联并不总是按照在查询中指定的顺序进行。决定关联的顺序是优化器很重要的一部分功能,本章后面将深入介绍这一点。 **将外连接转化成内连接** 并不是所有的OUTER JOIN语句都必须以外连接的方式执行。诸多因素,例如WHERE条件、库表结构都可能会让外连接等价于一个内连接。 MySQL能够识别这点并重写查询,让其可以调整关联顺序。 **内连接** 内连接是从结果表中删除与其他被连接表中没有匹配行的所有行,所以内连接可能会丢失信息。 **使用等价变换规则** MySQL可以使用一些等价变换来简化并规范表达式。它可以合并和减少一些比较,还可以移除一些恒成立和一些恒不成立的判断。例如,(5=5 AND a>5)将被改写为a>5。类似的,如果(a<b AND b=c) AND a=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”。果要查找一个最大值,也只需读取B-Tree索引的最后一条记录。如果MySQL使用了这种类型的优化,那么在EXPLAIN中就可以看到“Select tables optimized away”。从字面意思可以看出,它表示优化器已经从执行计划中移除了该表,并以一个常数取而代之。没有任何WHERE条件的COUNT(*)查询通常也可以使用存储引擎提供的一些优化(例如,MyISAM维护了一个变量来存放数据表的行数)。