当我们希望MySQL能够以更高的性能运行查询时,最好的办法就是弄清楚MySQL是如何优化和执行查询的。一旦理解了这一点,很多查询优化工作实际上就是遵循一些原则让优化器能够按照预想的合理方式运行
当我们想向MySQL发送了一个请求时,MySQL到底做了什么:
MySQL客户端和服务器之间的通信协议是“半双工”的,这意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。所以,我们无法也无需将一个消息切成小块独立来发送。
这种协议虽然让MySQL通信简单快速,但是也从很多地方限制了MySQL。一个明显的限制是,这意味着没法进行流量控制。一旦一端开始传送信息,另一端要接收完整个信息才能够响应它。
客户端使用一个单独的数据包将查询传给服务器,当查询语句太长时,服务端就会拒绝接受更多的数据并且抛出相应的错误。因此,当查询语句很长时,参数max_allowed_packet就特别重要。
相反的,一般服务器响应给用户的数据通常很多,由多个数据包组成。当服务器开始相应客户端请求时,客户端必须完整的接受整个返回结果,而不能简单的只取前几条结果。
换而言之,当客户端从服务器获取数据时,MySQL会一直向客户端推送数据,客户端也没法让服务器停下来。
对于一个MySQL连接或者说一个线程,任何时刻都有一个状态,该状态表示了MySQL当前正在做什么:
在解析一个查询语句之前,如果查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据,这个检查是通过一个对大小写敏感的哈希查找实现的。
查询和缓存中的查询即使只有一个字节不同,也不会匹配缓存结果。这种情况下查询就会进入下一个阶段。
如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前MySQL会检查一次用户权限。这仍然是无需解析查询SQL语句的,因为在查询缓存中已经存放了当前查询所需要访问的表信息。
如果权限没有问题,MySQL就会跳过所有其他阶段,直接从缓存表中拿到结果并且返回给客户端。在这种情况下,查询不会被解析,不用生成执行计划,不会被执行。
查询的生命周期的下一步是将一个SQL转换成一个执行接话,MySQL再按照这个执行计划和存储引擎进行交互。
这包括多个子阶段:解析SQL、预处理、优化SQL执行接话。
这些过程中任何出错都可能终止查询。
在解析和优化阶段,MySQL将会生成查询对应的执行接话,MySQL的查询执行引擎则根据这个执行计划来完成整个查询。这里的执行计划是一个数据结构,而不是其他很多关系型数据库那样的字节码。
相对于查询优化阶段,查询执行阶段并不那么复杂:MySQL只是简单的根据执行计划给出的指令逐步执行。在根据执行计划逐步执行的过程中,有大量的操作需要通过调用存储引擎实现的接口来完成。
查询执行的最后一个阶段是将结果返回给客户端。及时查询不需要返回结果给客户端,MySQL仍然会返回这个查询的信息,如该查询影响到的行数。
在这个阶段中,如果查询是可以被缓存的,那么MySQL在这个阶段也将会被存放到查询缓存中。
MySQL将结果集返回给客户端是一个增量地、逐步返回的过程。这样做有两个好处:服务器端无需存储太多的结果,也就不会因为要返回太多结果而消耗太多内存。这样的处理也会让MySQL客户端第一时间获得返回的结果。
结果集中的每一行都会以一个满足MySQL客户端/服务器通信协议的封包发送,再通过TCP协议进行传输,在TCP传输中,可能会对MySQL的封包进行缓存然后批量传输。