二、重构查询
有些时候我们需要重写查询以获取更好的性能,尽管得到的结果可能不同,也许最终程序的代码也会和查询一起被改。
1、是否可以把一个耗时的复杂查询分解成多个简单的查询。
> 平时我们更倡导用尽可能少的查询做尽可能多的事情,这样可以减少网络通信开销,能减少查询解析和优化的步骤,以及代码上似乎更优雅。
但是在MySql中,MySql被设计成可以很高效地连接和断开服务器,而且能很快地响应精简的查询。在现代网络下,MySql在一般的服务器上每秒钟可以处理50000个查询。因此,对于一些耗时的复杂查询,可以通过分解查询以得到更高的效率。
2、分解联接,把一个多表联接分解成多个单表查询,然后在应用程序端实现联接
> 例如有如下的一个连接查询:
SELECT * FROM tag JOIN tag_post ON tag.id = tag_post.tag_id WHERE tag.title = 'test';
分解成两个查询:
SELECT * FROM tag WHERE tag.title = 'test'; -- 假设返回id有 (10,11,12,13,14,15);
SELECT * FROM tag_post WHERE tag_id IN (10,11,12,13,14,15);
这样分解查询,看似浪费,但其针对一些耗时的多表联接能带来很好的性能提升:
》 缓存的性能更高:上面的查询已经被缓存起来,下次再查询tag.title = 'test',则会直接从缓存中取出;第二条IN操作,下次查询(11,12,14, 20,25),对于11,12,14则直接从缓存中取出,只去读取20,25。如果一个表经常改变,分解联接可以减少缓存失效的次数。
》 可以减少多余的行访问,联接操作,每从tag表中检查一行,就会去tag_post中去检查。
> 什么时候使用分解联接更好: 可以缓存早期查询的大量数据 , 数据分布在不同的服务器上 , 对于大表使用IN()替换联接 三、MySql如何优化和执行查询
下面这幅图显示了查询的执行路径:
① 客户端将查询发送到服务器;
② 服务器检查查询缓存,如果找到了,就从缓存中返回结果,否则进行下一步。
③ 服务器解析,预处理和优化查询,生成执行计划。
④ 执行引擎调用存储引擎API执行查询。
⑤ 服务器将结果发送回客户端。
1、客户端将查询发送到服务器
>首先需要知道,客户端用一个数据包将查询发送到服务器,一旦客户端发送了查询,剩下的就是等待结果。如果一个查询过大,比如批量插入,有时会出现"MySQL server has gone away"的错误,导致的原因可能就是传送的数据太大,导致连接断开了,可以通过 SHOW VARIABLES LIKE "max_allowed_packet" 命令查看你的服务器所允许传送的最大数据,可在my.ini里配置。
> 服务器发送的响应由许多数据包组成,服务器发送响应的时候客户端必须接收完整的结果集,不能只提取几行数据后要求服务器停止发送剩下的数据。所以,使用LIMIT来获取你所需要的数据行数。
> 每个MySql连接,或者叫线程,在任意一个给定的时间都有一个状态来标识正在进行的事情。可以使用 SHOW [FULL] PROCESSLIST 命令来查看哪些线程正在运行,及其查询状态,Command列显示了状态。
一些常见的状态:其它的查找MySql手册
Sleep 线程正在等待客户端,以向它发送一个新语句
Query 线程正在执行查询或往客户端发送数据
Locked 该查询被其它查询锁定
Copying to tmp table on disk 临时结果集合大于tmp_table_size。线程把临时表从存储器内部格式改变为磁盘模式,以节约存储器
Sending data 线程正在为SELECT语句处理行,同时正在向客户端发送数据
Sorting for group 线程正在进行分类,以满足GROUP BY要求
Sorting for order 线程正在进行分类,以满足ORDER BY要求
2、服务器检查查询缓存
> 在解析一个查询之前,如果开启了缓存,MySql会检查查询缓存,进行大小写敏感的哈希查找。即使查询和缓存中的查询只有一个字节的差异,也表示不匹配,查询就会进入下一步。
> MySql查询缓存保留了查询返回给客户端的完整结果,当缓存命中的时候,服务器马上返回保存的结果(会先检查权限),并跳过解析、优化和执行步骤。查询缓存保留了查询使用过的表,如果表发生了改变(如update),那么缓存的数据就失效了。
3、服务器解析、优化,生成执行计划
> 如果查询缓存中没有,下一步就是将查询转变成执行计划,包括解析、预处理和优化的过程。这个过程的任何一步都有可能出现错误,比如语法错误等。这里我们可以看到平时出现的大部分错误是从哪一步抛出来的。
> 首先是解析器将查询分解成一个个标识,然后构造一颗“解析树”,解析器保证查询中的标识都是有效的,会检查其中的基本错误,比如字符串上面的引号没有闭合等。
> 然后预处理器检查解析器生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。最后,预处理器检查权限。
> 之后,优化器把解析树变成执行计划。一个查询通常可以有很多种执行方式,并且返回同样的结果,优化器的任务就是找到最好的方式。
》 MySql使用的是基于开销的优化器。它会预测不同的执行计划的开销,并且选择开销最小的一个。可以使用 SHOW STATUS LIKE "Last_query_cost" 命令查看查询的开销(但不能作为绝对标准)。如下表名最近一个查询会造成29728次随机读取。
》 但是优化器并不总是能选择最好的方案。比如统计数据可能是错误的,服务器依赖于存储引擎提供的统计,它可能很准确,可能很不准确。再比如优化器不会估算每一个可能的执行计划,所以它可能会错过优化方案。
》 MySql执行计划是树形结构,目的是指导执行引擎产生结果。最终的计划中包含了足够的信息来重建查询。可以对某个查询使用EXPLAIN EXTENDED 命令,并在结尾加上 SHOW WARNINGS,就可以看到重建后的查询。
下图中,结果1显示了查询执行计划,结果2中Message显示了MySql优化后的查询语句,也是最终执行的语句,可以复制出来看看。