6.查询性能优化
6.1 为什么查询速度会慢
6.2 慢查询基础:优化数据访问
查询性能低下最重要的原因是访问的数据太多。大部分性能低下的查询都可以通过减少访问的数据量的方式优化。
对于低效的查询,我们发现通过下面的2个步骤来分析总是很有效的:
1.确认应用程序是否在检索大量超过需要的数据
2.确认mysql服务器层是否在分析大量超过需要的数据行
6.2.1 是否向数据库请求了不需要的数据
1.查询不需要的记录
2.多表关联时返回全部列
3.总是取出全部列
4.重复查询相同的数据
6.2.2 mysql 是否在扫描额外的记录
查看查询为了返回结果是否扫描了过多的数据,mysql衡量查询开销的3个指标:
1.响应时间
响应时间是2个部分之和:服务时间和排队时间。服务时间是指数据库处理这个查询真正花费的时间。排队时间是指服务器因为等待某些资源而没有
真正执行查询的时间---可能是等IO完成,也可能是等待行锁,等等。
2.扫描的行数
较短的行的访问速度比较快,内存中的行也比磁盘上的行访问速度要快。
3.返回的行数
mysql 能够使用如下3种方式应用 where 条件,从好到坏:
1.在索引中使用 where 条件过滤不匹配的记录。这是在存储引擎层使用的
2.使用覆盖索引(在 Extra 列中出现了 Using Index) 来返回记录,直接从索引中过滤不需要的记录返回命中的结果。这是在mysql服务器层实现的,
但无需回表查询记录
3.从数据表中返回数据,然后过滤不满足条件的记录(在 Extra 中出现了 Using Where)。这是在 mysql 服务器层实现的,mysql 需要先从数据表中
读取数据然后再过滤。
如果发现查询需要扫描大量的数据但是只返回少数的行,那么通常可以使用下面的手段优化:
1.使用索引覆盖扫描,把所有需要用到的列都放到索引中,这样存储无需回表获取对应的行就可以返回结果了。
2.改变库表结构,如单独使用汇总表
3.重写查询
6.3 重构查询方式
6.3.1 一个复杂的查询还是多个简单的查询
以前网络通信,查询解析和优化是件代价很高的事情。现在开销小。
6.3.2 切分查询
一次操作1w条数据一般来说是一个比较高效而且对服务器影响也最小的。
6.3.3 分解关联查询
对关联查询进行分解,可以对每个表进行一次单表查询,然后将结果在应用层进行关联。优势:
1.让缓存效率更高
2.分解后,执行单个查询可以减少锁的竞争
3.在应用层做关联,可以更容易的对数据库进行拆分,更容易做到高性能和可扩展
4.查询本身效率也可以提升
5.可以减少冗余记录的查询
6.这相当于在应用层实现哈希关联,而不是使用mysql的嵌套循环关联。某些场景哈希关联的效率要高很多。
6.4 查询执行的基础
查询的执行路径:
客户端 => 查询缓存 => 解析器 => 解析树 => 预处理器 => 查询优化器 => 查询执行计划 => 查询执行引擎 => api接口调用 => 存储引擎
1.客户端发送一条查询给服务器
2.服务器先检查查询缓存,如果命中了缓存,则立即返回存储在缓存中的结果。否则进入下一阶段
3.服务器端进行sql解析,预处理,再由优化器生成对应的执行计划
4.mysql 根据优化器生成的执行计划,调用存储引擎的api来执行查询
5.将结果返回给客户端
6.4.1 mysql 客户端/服务器通信协议
mysql客户端和服务器之间的通信协议是'半双工'的,这意味着任意时刻,要么是服务器向客户端发送数据,要么是由客户端向服务器发送数据,这2个动作
不能同时发生。所以,我们无法也无需将一个消息切成一小块独立来发送。这种协议让mysql通信简单快速,但是也从很多方面限制了mysql。一个明显的限制
就是没法进行流量控制。一旦一端开始发送消息,另外一端要接收完整消息才能响应它。
客户端使用一个单独的数据包将查询发送给服务器。这也是为什么当查询的语句很长的时候,参数 max_allowed_packet 就特别重要了。如果查询太大,服务器
会拒绝接收更多的数据并抛出相应错误。
相反,一般服务器响应给用户的数据通常很多,由多个数据包组成。当服务器开始响应客户端请求时,客户端必须完整的接收整个返回结果,而不能简单的只取前面几条
结果,然后让服务器停止发送数据。这也是为什么在必要的时候一定要在查询中加上 limit 限制的原因。
多数连接mysql 的库函数都可以获得全部结果集并缓存到内存里,还可以逐行获取需要的数据。默认一般是全部结果集缓存到内存中。mysql 通常是等到所有的数据都发送
给客户端才能释放这条查询所占用的资源,所以接收全部结果并缓存通常可以减少服务器的压力,让查询快点结束。
当使用多数mysql的库函数连接mysql的时候,其结果看起来都像是从mysql服务器获取数据,而实际上都是从这个库函数的缓存中获取数据。多数情况下这没有什么问题,
但是如果需要返回一个很大的结果集的时候,这样做并不好,因为库函数会花很大时间和内存来存储所有的结果集。如果能尽早的处理这些结果集,就能减少内存的消耗,这种
情况下可以不使用缓存来记录结果而直接处理。这样做的缺点是,对于服务器来说,需要查询完成后才释放资源,所以在客户端的整个交互过程中,服务器的资源一直被占用。
mysql_query();
mysql_unbuffered_query(); // 不缓存
查询状态:
对于一个mysql连接,或者说一个线程,任何时刻都有一个状态。该状态表示了mysql 正在做什么。
show full processlist;
Sleep : 线程正在等待客户端发送新的请求
Query : 线程正在执行查询或者正在将查询发送给客户端
Locked : 在mysql服务器层,该线程正在等待表锁。在存储引擎级别实现的锁,例如 InnoDB 的行锁,并不会体现在线程状态中。对于MyISAM 来说是一个比较典型的状态,
但在其他没有行锁的引擎中也经常会出现。
Analyzing and statistics : 线程正在收集存储引擎统计信息,并生成查询的执行计划
Copying to tmp table [on disk] : 线程正在做执行查询,并且将结果集都复制到一个临时表中,这种状态一般要么是在做 group by 操作,要么是做文件排序,或者
是 union 操作。如果后面有 on disk,那么mysql 正在将一个内存临时表放到磁盘上。
Sorting result : 线程正在对结果集进行排序。
Sending data : 这种情况表示很多,线程可能在多个状态之间传送数据,或者生成结果集,或者向客户端发送数据。
6.4.2 查询缓存
在解析一个查询语句之前,如果查询缓存是开的,那么mysql会优先检查是否命中查询缓存中的数据。这个查询是通过一个堆大小写敏感的哈希查找实现的。查询和缓存中的查询
即使只有一个字节的不同,那么也不会匹配结果。
如果当前的查询恰恰命中了查询缓存,那么在返回查询结果之前mysql 会检查一次用户权限。这仍然是无需解析查询sql的,因为在查询缓存中存放了当前查询需要访问的表信息。
如果权限没有问题,mysql 会跳过所有其他的阶段,直接从缓存中拿到结果并返回客户端。
6.4.3 查询优化处理
查询的生命周期的下一步是将一个 sql 转换成一个执行计划,mysql再依照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析sql,预处理,优化sql执行计划。这个过程
中任何错误都可能终止查询。
语法解析器和预处理:
首先,mysql通过关键字将sql语句进行解析,并生成一颗对应的'解析树'。mysql解析器将使用mysql语法验证和解析查询。例如,它验证是否使用错误的关键字,或者关键字的顺序
是否正确等。
预处理会根据一些mysql规则进一步检查解析树是否合法,例如,这里将检查数据表和数据列是否存在,还会解析名字和别名,看它们是否有歧义。下一步,预处理会验证权限。
查询优化器:
现在语法树被认为是合法的了,并且由优化器将其转化成执行计划。一条查询可以有很多种执行方式,最后都返回相同的结果。优化器的作用就是找到这其中最好的执行计划。
mysql 使用基于成本的优化器,它将尝试预测一个查询使用某种查询计划时的成本,并选择其中成本最小的一个。可以通过查询当前会话的 Last_query_cost 的值来得知
mysql计算的当前查询的成本。
show status like 'last_query_cost';
+-----------------+----------+
| Variable_name | Value |
+-----------------+----------+
| Last_query_cost | 1040.799000 |
+-----------------+----------+
这个结果表示 mysql 的优化器认为大概需要做 1040 个数据页的随机查找才能完成上面的查询。这是根据一系列的统计信息得到的:每个表或者索引的页面个数,索引的基数(
索引中不同值的数量),索引和数据行的长度,索引分布情况。优化器在评估的成本的时候,并不考虑任何层面的缓存,它假设读取任何数据都需要一次磁盘IO.
有很多原因会导致mysql优化器选择错误的执行计划,比如:
1.统计信息不准确。
2.执行计划中的成本估算不等同于实际执行的成本。
3.mysql的最优可能和你想象的最优不一样。
4.mysql不考虑其他并发执行的查询。
5.mysql也不是任何时候都基于成本优化的。有时候也会基于一定的规则。
6.mysql不会考虑不受其控制的操作的成本。
7.优化器有时候无法估算所有可能的执行计划,所以可能错过实际上最优的。
mysql 的查询优化器是一个非常复杂的部件,它使用了很多策略来生成一个最优的执行计划。优化策略可以分为2种:
1.静态优化
可以直接对解析树进行分析,并完成优化。例如,优化器可以通过一些简单的代数变换将 where条件转换成另外一种等价形式。静态优化不依赖于特别的数值,如where条件
中带入的一些常数等。静态优化器在第一次完成后就一直有效,即使使用不同的参数重复查询也不会发生变化。可以认为是一种 '编译时优化'。
2.动态优化
与查询的上下文有关,也可能和其他很多因素有关,例如where条件中的取值,索引中条目对应的数据行等。这需要在每次查询的时候都重新评估,可以认为是一种 '运行时优化'。
下面是mysql能处理的一些优化类型:
1.重定义关联表的顺序
2.将外连接转化为内连接
3.使用等价变换规则
4.优化 count(), min()和 max()
5.预估并转化为常数表达式
6.覆盖索引扫描
7.子查询优化
8.提前终止
9.等值传播
10.列表in()的比较
在很多数据库中,in() 完全等价于多个 or 条件的子句,因为这2者是完全等价的。在mysql中这点是不成立的,mysql将 in()列表中的数据先进行排序,
然后通过二分查找的方式来确定列表中的值是否满足条件,这是一个O(log n)复杂度的操作,等价的转换为or查询的复杂度为O(n),对于IN()列表中有大量
取值的时候,mysql的处理速度将会更快。
数据和索引信息统计:
mysql架构由多个层次组成,在服务器层有查询优化器,却没有保存数据和统计信息。统计信息由存储引擎实现,不同的存储引擎可能会存储不同的统计信息。
某些引擎则没有。
因为服务器层没有统计任何信息,所以mysql查询优化器在生成查询计划的时候,需要向存储引擎获取相应的统计信息。存储引擎则对应提供给优化器对应的
统计信息,包括:每个表或者索引有多少页面,每个表的每个索引的基数是多少,数据行和索引长度,索引的分布信息等。优化器根据这些信息来选择一个最优
的执行计划。
MySQL如何执行关联查询:
mysql中'关联'一次所包含的意义比一般意义上的理解要更广泛。总的来说,mysql认为任何一个查询都是一次'关联' --- 并不仅仅是一个查询需要到2个表的
匹配才叫关联,所以在mysql中,每个查询,每一个片段(包括子查询,甚至单表 select)都可能是关联。
对于 union 查询,mysql 先将一系列的单个查询结果放到一个临时表中,然后再重新读出临时表数据完成 union 查询。在mysql的概念中,每个查询都是一次
关联,所以读取结果临时表也是一次关联。
mysql关联的执行策略也很简单:mysql对任何关联都执行嵌套关联操作,即mysql先在一个表中旬取出单条数据,然后再嵌套到下一个表中寻找匹配的行,依次下去,
直到找到所有表中匹配的行为止。然后根据各个匹配的行,返回查询中需要的各个列。mysql会尝试在最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到
更多的行,mysql 返回到上一层次关联表,看是否能够找到更多匹配记录,以此类推迭代执行。
按照这样的方式查找第一个表的记录,再嵌套查询下一个关联表,然后回溯到上一个表,在mysql中是通过嵌套循环的方式实现的---正如其名'嵌套循环关联'。
从本质上来说,mysql对所有的类型的查询都是以同样的方式进行。例如,mysql在from子句中遇到子查询时,先执行子查询并将其结果存放到一个临时表中,然后将
这个临时表当作一个普通表对待(正如其名'派生表')。mysql在执行union查询的时候也使用类似的临时表,在遇到右外连接的时候,mysql将其改写成等价的左外连接。
简而言之,当前mysql版本会将所有的查询类型都转换为类似的执行计划。
不过,不是所有的查询都可以转换成上面的形式。例如,全外连接就无法通过嵌套查询和回溯的方式完成,这时当发现关联表中没有找到任何能匹配的行的时候,则可能是
因为关联是恰好从一个没有任何匹配的表开始。这大概也是mysql不支持全外连接的原因。
mysql 的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候需要注意这一点,这一点对 Union查询也一样。
执行计划:
和很多其他关系数据库不同,mysql并不会生成查询字节码来执行查询。mysql生成查询的一颗指令数,然后通过存储引擎执行完成这颗指令数并返回结果。最终的查询计划
包含了重构查询的全部信息。如果对某个查询执行 explain extended 后,再执行show warnings,就可以看到重构出的查询了。
关联查询优化器:
mysql优化器最重要的一部分就是关联查询优化,它决定了多个表关联时的顺序。通常多表关联时,可以有多种不同的关联顺序来获得相同的执行结果。关联查询优化器则通过
评估不同的顺序时的成本来选择一个代价最小的关联顺序。
重新定义关联的顺序是优化器非常重要的一部分功能。不过有的时候,优化器给出的并不是最优的关联顺序。这个时候可以使用 straight_join 关键字重写查询,让优化器
按照你认为的最优的关联顺序执行。
关联优化器会尝试在所有的关联顺序中选择一个成本最小的来生成执行计划树。如果可能,优化器会遍历每个表然后逐个做嵌套循循环计算每一棵可能的执行计划数的成本,
最后返回一个最优的执行计划。
不过,糟糕的是,如果有超过n个表的关联,那么需要检查n的阶乘种关联顺序。我们称之为所有可能的执行计划的'搜索空间',搜索空间的增长速度非常快---例如,若是10个表
的关联,那么共有 3628800种不同的关联顺序。当搜索空间非常大的时候,优化器不可能逐一评估每一种关联顺序的成本。这时候,优化器选择使用'贪婪'搜索的方式查找'最优'
的关联顺序。实际上,当需要关联的表超过 optimizer_search_depth 的限制的时候,就会选择 '贪婪'搜索模式了。
排序优化:
无论如何排序都是一个成本很高的事情,所以从性能角度考虑,应该尽可能避免排序或者尽可能避免对大量数据进行排序。
当不能使用索引进行排序的时候,mysql需要自己进行排序,如果数据量少则在内存中排序,如果数据量大则需要使用磁盘排序。不过,mysql将这个过程统一称为'文件排序'。
即使完全是内存不需要任何磁盘文件也是如此。
如果需要排序的数据量小于'排序缓冲区',mysql使用内存进行'快速排序'操作。如果内存不够排序,那么mysql会先将数据分块,对每个独立的块使用 '快速排序'进行排序,
并将各个块的排序结果存放在磁盘上,然后将各个排好的序的块进行合并,最后返回排序结果。
mysql有如下2种排序算法:
1.两次传输排序(旧版本使用)
2.单词传输排序(新版本使用)
mysql 在进行文件排序的时候需要使用的临时空间可能比想象中的大很多。原因在于mysql在排序的时候,对每一个排序记录都会分配一个足够长的定长空间来存放。
这个定长空间必须足够长以容纳其中的最长的字符串。例如,如果是varchar列则需要分配其完整长度;如果使用的是 utf-8字符集,那么mysql将会为每个字符预留3个
字节。
在关联查询的时候如果需要排序,mysql会分2种情况来处理这样的文件排序。如果order by 子句中所有的列都来自关联的第一个表,那么mysql在关联处理第一个表
的时候就进行文件排序。如果是这样,mysql的explain结果中可以看到 Extra 字段显示 'Using filesort'。除此之外的所有情况,mysql 都会先将关联的结果存放
到一个临时表中,然后在所有的关联都结束之后,再进行文件排序。这种情况下,explain Extra 显示 'Usnig temporary; Using filesort'。如果查询中有limit
的话,limit 也会在排序之后应用,所以即使需要返回较少的数据,临时表和需要排序的数据量仍然会非常大。
6.4.4 查询执行引擎
在解析和优化阶段,mysql将生成查询对应的执行计划,mysql的查询执行引擎则根据这个执行计划来完成整个查询。这里的执行计划是一个数据结构,而不是和很多其他的
关系型数据库那样会生成对应的字节码。
相对于查询优化阶段,查询执行阶段不是那么复杂:mysql只是简单的根据执行计划给出的指令逐步执行。在根据执行计划逐步执行的过程中,有大量的操作需要通过调用
存储引擎实现的接口来完成,这些接口也就是被我们称之为'handler API'的接口。在查询中的每一个表由一个 handler 的实例表示。实际上,mysql在优化阶段就为每个
表创建了一个handler实例,优化器根据这些实例的接口可以获取表的相关信息,包括表的所有列名,索引统计信息等。
存储引擎接口有着非常丰富的功能,但是底层接口却只有几十个。
并不是所有的操作都由handler完成,例如,当mysql需要进行锁表的时候。handler可能会实现自己的级别的,更细粒度的锁。如InnoDB就实现了自己的行基本锁,但这个
并不能代替服务器层的表锁。
6.4.5 返回结果给客户端
查询执行的最后一个阶段是将结果返回给客户端。即使查询不需要返回结果集给客户端,mysql仍然会分返回这个查询的一些信息,比如该查询影响到的行数。
如果查询可以被缓存,那么mysql会在这个阶段将结果集存放到缓存中。
mysql将结果集返回客户端是一个增量,逐步返回的过程。例如,我们前面看到的关联操作,一旦服务器完成最后一个关联表,开始生成第一条结果时,mysql就
开始向客户端逐步返回结果集了。
这样做有2个好处:服务器端无需存储太多的结果,也就不会因为要返回太多的结果而消耗内存。另外,这样的处理也让mysql客户端第一时间获取返回的结果。
结果集中的每一行都会以一个满足mysql客户端/服务器通信协议的封包发送,再通过tcp协议传输,在tcp协议传输的过程中,可能对mysql的封包进行缓存然后
批量发送。
6.5 MySQL查询优化器的局限性
MySQL的万能 '嵌套循环'并不是对每种查询都是最优的。不过还好,mysql查询优化器只对少部分查询不适用,而且我们往往可以通过改写查询让mysql高效的完成工作。
6.5.1 关联子查询
mysql的子查询实现的非常糟糕。最糟糕的一类是where 条件中包含 in() 的子查询。如 select ... where film_id in (select * ...)。mysql 会将
相关的外层表压到子查询中,它认为这样可以高效的查找到数据行。也就是说,它会改写成: select ... where film_id exists (select * ...)
可以改写成连接查询。
如何利用好关联子查询:
并不是所有的关联子查询的性能都会很差。
6.5.2 union 的限制
有时,mysql无法将限制条件从外层'下推'到内层,这使得原本能够限制部分返回结果的条件无法应用到内存查询的优化上。
如果希望 uinon的各个子句能够根据 limit 只取部分结果集,或者希望能够先排好序再喝吧结果集的话,就需要对union各个子句中分别使用这些子句。
6.5.3 索引合并优化
当where 子句中包含多个复杂条件的时候,mysql能够访问单个表的多个索引以合并和交叉过滤的方式来定位需要查找的行。
6.5.4 等值传递
6.5.5 并行执行
mysql无法利用多核特性来并发执行查询。
6.5.6 哈希关联
mysql并不支持哈希关联---mysql所有的关联都是嵌套循环管理。不过,可以建立一个哈希索引来曲线的实现哈希关联。
6.5.7 松散索引扫描
mysql并不支持松散索引扫描,也就无法按照不连续的方式扫描一个索引。
6.5.8 最大值和最小值优化
对于max(),min()查询,mysql做的并不好。
6.5.9 在同一个表上进行查询和更新
mysql不允许对同一张表同时进行查询和更新。可以使用生成表的方式绕过这个限制。
6.6 查询优化器的提示(hint)
如果对优化器选择的执行计划不满意,可以使用优化器提供的几个提示器(hint)来控制最终的执行计划。
HIGH_PRIORITY 和 LOW_PRIORITY // 哪些语句的优先级比较高,哪些比较低,这2个只对表锁的存储引擎有效
DELAYED //这个提示对insert和replace有效。mysql会将使用该提示的语句立即返回给客户端,并将插入的行数据放到缓冲区,然后
在表空闲的时候批量将数据插入。
STRAIGHT_JOIN //这个提示可以放置在 select关键字之后,也可以放在任何两个关联表的名字之间。第一个用法是让查询中所有的表按照在
语句中出现的顺序进行关联。第二个用法则是固定其前后两个表的关联顺序。
SQL_SAMLL_RESULT 和 SQL_BIG_RESULT //这个提示只对select有效,它们告诉优化器对group by或者distinct查询如何使用临时表及排序。
SQL_SAMLL_RESULT告诉优化器结果集会很小,可以将结果集放在内存中的索引临时表,以避免排序。如果是SQL_BIG_RESULT,则告诉优化器结果集
可能会很大,建议使用磁盘临时表做排序操作。
SQL_BUFFER_RESULT //告诉优化器将结果存放到一个临时表中,然后尽可能快的释放表锁。
SQL_CACHE 和 SQL_NO_CACHE //告诉mysql这个结果集是否应该缓存在查询缓存中。
SQL_CALC_FOUND_ROWS //它会让mysql返回的结果集包含更多的信息。
FOR UPDATE 和 LOCK IN SHARE MODE //这2个提示主要控制select语句的锁机制,但只对实现了行级锁的存储引擎有效。使用该提示会对符合查询条件的
数据行进行加锁。这2个提示会让某些优化无法正常使用,如覆盖索引。InnoDB不能在不访问主键的情况下排他的锁定行,因为行的版本信息是保存在主键中的。
USE INDEX, IGNORE INDEX 和 FORCE INDEX //告诉优化器使用或者不使用哪些索引来查询记录
optimizer_search_depth //这个参数控制优化器在穷举执行计划时的限度。如果查询长时间处于'Statistics'状态,可以调低此参数。
optimizer_prune_level //优化器会根据需要扫描的行数来决定是否跳过某些执行计划
optimizer_switch //这个变量包含了一些开启/关闭优化器的标志位。
6.7 优化特定类型的查询
6.7.1 优化 count() 查询
count()是一个特殊的函数,由2种非常不同的作用:它可以统计某个列值的数量,也可以统计行数。在统计列值时要求列值是非空的(不统计null)。
如果在 count()是括号中指定列或者列的表达式,则统计的就是这个表达式有值的结果数。count()的另外一个作用是统计结果集的行数。当mysql确认
括号内的表达式不可能为空时,实际上就是统计行数。最简单的就是当我们使用count(*)的时候,这种情况下通配符* 并不会像我们猜测的那样扩展成
所有的列,实际上,它会忽视所有的列,而直接统计所有的行数。
关于MyISAM的神话:
一个容易产生误解的就是:MyISAM的count()函数总是非常快,不过这是有前提条件的,即只有没有任何where条件的 count(*)才快,因为此时
无需实际的去计算表的行数。mysql 可以利用存储引擎的特性直接获得这个值。如果mysql知道某列col不可能为null值,那么mysql内部会将count(col)
表达式优化为 count(*)。
当统计带 where 子句的结果集行数,可以是统计某个列值的数量时,MyISAM的count()和其他存储引擎没有任何不同,就不再有神话般的速度了。
//统计不同颜色的数量
select sum( if( color = 'blue'), 1, 0) as blue, sum( if(color = 'red'), 1, 0) as red from items;
//也可以使用 count() 而不是 sum() 实现同样的目的
select count(color='blue' or null) as blue, count(color='red' or null) as red from items;
6.7.2 优化关联查询
1.确保 on 或者 using 子句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。
2.确保任何的 group by 和 order by 中的表达式只涉及到一个表中的列,这样mysql才有可能使用索引来优化这个过程。
3.当升级mysql的时候需要注意:关联语法,运算符优先级等其他可能会发生变化的地方。
6.7.3 优化子查询
尽可能使用关联查询代替。
6.7.4 优化 group by 和 distinct
在mysql中,当无法使用索引的时候,group by 使用2种策略来完成:使用临时表或者文件排序来分组。对于任何查询语句,这2种策略的性能都有可以提升的地方。
可以通过提示 SQL_BIG_RESULT 和 SQL_SAMLL_RESULT 来让优化器按照你希望的方式运行。
如果需要对关联查询做分组(group by),并且是按照查找表中的某个列进行分组,那么通常采用查找表的标识符(主键)分组的效率会比其他类更高。
如果没有通过 order by 子句显式指定排序列,当查询使用group by 子句的时候,结果集会自动按照分组的字段进行排序。如果不关心结果集的排序,而这种默认
排序又导致了需要文件排序,则可以使用 order by null,让mysql不再进行文件排序。也可以在 group by 子句中直接使用 desc 或者asc关键字,让分组的结果集
按照需要的方向排序。
优化group by with rollup
6.7.5 优化limit分页
如果有对应的索引,通常效率不错,否则,mysql需要做大量的文件排序操作。
当偏移量比较大的时候,比如 limit 10000,20 这样的查询,这时mysql需要查询10020条记录后,返回20条。前面的10000条都被抛弃了,这样代价很高。
尽可能的使用覆盖索引,而不是查询所有的列。然后根据需要做一次关联操作再返回需要的列。
有时候也可以将 limit 查询转换为已知位置的查询,例如 select * from 表名 where id < 16000 order by id limit 20;
还有就是使用冗余表,汇总表。
6.7.6 优化 SQL_CALC_FOUND_ROWS
分页的时候,另一个常用的技巧是在 limit 语句中加上SQL_CALC_FOUND_ROWS提示,这样就可以获得去掉limit以后满足条件的函数,因此可以作为分页的总数。
实际上,mysql只有在扫描了所有满足条件的行以后,才会知道行数,所以加上这个限制之后,不管是否需要,mysql都会扫描所有满足条件的行,然后再抛弃不需要的行,
而不再满足 limit 的行数就终止扫描。所以该提示的代价比较高。
一个更好的设计是将具体的页数换成 '下一页'按钮,假设每页显示20条记录,那么我们每次查询时都使用limit 返回21条并只显示20条,如果21条存在,那么就显示
'下一页'。
另外一种做法是先获取并缓存较多的数据。
有时候也可以考虑使用 explain 中的rows做的值作为结果集总数的近似值。
6.7.7 优化union查询
mysql 总是通过创建并填充临时表的方式来执行union查询。因此很多优化策略对union查询中都没有办法使用。经常需要手动的将where,limit,order by 等
子句'下推'到union的各个子句中。
除非确实需要服务器消除重复的行,否则就一定要使用union all,这一点很重要。如果没有all关键字,mysql会给临时表加上distinct选项,这会导致对整个临时表
的数据做唯一性检查。这样做的代价非常高。即使使用all关键字,mysql仍然会使用临时表存储结果。事实上,mysql总是将结果放入临时表,然后再读出,再返回给客户端。
6.7.8 静态查询分析
pt-query-acvisor 能够解析查询日志,分析查询模式,然后给出所有可能存在的问题,并给出建议。
6.7.9 使用用户自定义变量
在查询中混合使用过程化和关系化逻辑的时候,自定义变量可能会非常有用。用户自定义变量是一个用来存储内容的临时容器,在连接mysql的整个过程中都存在。可以使用
set 和 select 语句来定义它们。
set @one := 1;
select @one;
哪些场景不能使用用户自定义变量:
1.使用自定义变量查询,无法使用查询缓存
2.不能在使用常量或者标识符的地方使用自定义变量,如表名,列名
3.用户自定义变量的生命周期是在一个连接中有效,所以不能用它们来做连接间的通信
4.如果使用连接池或持久化连接,自定义变量可能会让毫无关系的代码发生交互
5.在5.0之前的版本,是大小写敏感的。
6.不能显式的声明自定义变量的类型。
7.mysql优化器在某些场景下可能会将这些变量优化掉
8.赋值的顺序和赋值的时间点并不总是固定的,这依赖于优化器的决定
9.赋值符号 := 的优先级非常低
10.使用未定义的变量不会产生任何语法错误
其他用法:
1.查询运行时计算总数和平均值
2.模拟group语句中的函数 first()和last()
3.对大量数据做一些数据计算
4.计算一个大表的 md5 散列值
5.编写一个样本处理函数
6.模拟读/写游标
7.在show语句的where子句中加入变量值
1.优化数据访问,减少不必要的列(有时为了代码服用可以考虑select *)
2.扫描的行数和访问类型(全表扫描,索引扫描,范围扫描,唯一索引扫描,常数引用)
3.重构查询方式
切分查询(一次1w最佳)
分解关联查询然后在应用中组合(让缓存更高效,减少锁竞争,在应用层做关联可扩展性好,查询效率提升,减少冗余记录查询,相当于在应用层做哈希关联而不是mysql本身的嵌套循环关联)
三种方式应用 where 条件,从好到坏:
1.在索引中使用 where ,这是在存储引擎层实现
2.使用覆盖索引(Using index),直接从索引中过滤不需要的记录,在MySQL服务器层实现,无需回表
3.从数据表返回数据再过滤(Using where),在服务器层实现。
MySQL 查询过程:
客户端=>查询缓存=>解析器=>解析树=>预处理=>解析树=>查询优化器=>执行计划=>执行计划引擎=>api 存储引擎=>数据
mysql 半双工通信
查询状态(每个连接都有一个状态,show full processlist):
1.sleep : 线程正在等待客户端发送新的请求
2.query : 线程正在查询或者返回数据给客户端
3.locked : 在服务器层,等待表锁。在存储引擎实现的锁(行锁)并不会体现
4.analyzing and statistics : 线程正在收集存储引擎统计信息,并生成执行计划
5.copying to tmp table [on disk] : 线程正在查询,并将其结果复制到一个临时表中。这种状态要么在做 group by 操作,要么是文件排序操作,要么是 union 操作。on disk,将一个内存临时表存放到磁盘上。
6.sorting result : 对结果集进行排序
7.sending data : 线程可能在多个状态之间传送数据,或者在生成结果集,或者向客户端发送数据。
优化器:
1.静态优化(编译时优化,只做一次)
2.动态优化(运行时优化,每次执行时需要重新评估)
MySQL 两种排序算法:
1.两次传输排序(旧版本)
2.单次传输排序
关联查询如果需要排序,如果 order by 中的列全部来自第一张表,extra 为 using filesort.
除此之外的所有情况,MySQL会将关联的结果放到临时表中,再进行文件排序。extra 为 using temporary;using filesort
http://www.cnblogs.com/heat-man/p/4945708.html