MySql查询性能优化

避免向数据库请求不须要的数据

在访问数据库时,应该只请求须要的行和列。请求多余的行和列会消耗MySql服务器的CPU和内存资源,并增长网络开销。
例如在处理分页时,应该使用LIMIT限制MySql只返回一页的数据,而不是向应用程序返回所有数据后,再由应用程序过滤不须要的行。
当一行数据被屡次使用时能够考虑将数据行缓存起来,避免每次使用都要到MySql查询。
避免使用SELECT *这种方式进行查询,应该只返回须要的列。mysql

查询数据的方式

查询数据的方式有全表扫描、索引扫描、范围扫描、惟一索引查询、常数引用等。这些查询方式,速度从慢到快,扫描的行数也是从多到少。能够经过EXPLAIN语句中的type列反应查询采用的是哪一种方式。
一般能够经过添加合适的索引改善查询数据的方式,使其尽量减小扫描的数据行,加快查询速度。
例如,当发现查询须要扫描大量的数据行但只返回少数的行,那么能够考虑使用覆盖索引,即把全部须要用到的列都放到索引中。这样存储引擎无须回表获取对应行就能够返回结果了。算法

分解大的查询

能够将一个大查询切分红多个小查询执行,每一个小查询只完成整个查询任务的一小部分,每次只返回一小部分结果
删除旧的数据是一个很好的例子。若是只用一条语句一次性执行一个大的删除操做,则可能须要一次锁住不少数据,占满整个事务日志,耗尽系统资源、阻塞不少小的但重要的查询。将一个大的删除操做分解成多个较小的删除操做能够将服务器上本来一次性的压力分散到屡次操做上,尽量小地影响MySql性能,减小删除时锁的等待时间。同时也减小了MySql主从复制的延迟。
另外一个例子是分解关联查询,即对每一个要关联的表进行单表查询,而后将结果在应用程序中进行关联。下面的这个查询:sql

SELECT * FROM tag
    JOIN tag_post ON tag_post.tag_id=tag.id
    JOIN post ON tag_post.post_id=post.id
WHERE tag.tag = 'mysql';

能够分解成下面这些查询来代替:数据库

SELECT * FROM tag WHERE tag = 'mysql';
SELECT * FROM tag_post WHERE tag_id = 1234;
SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);

将一个关联查询拆解成多个单表查询有以下有点:缓存

  1. 让缓存的效率更高。若是缓存的是关联查询的结果,那么其中的一个表发生变化,整个缓存就失效了。而拆分后,若是只是某个表不多的改动,并不会破坏全部的缓存。
  2. 能够减小锁的竞争
  3. 更容易对数据库进行拆分,更容易作到高性能和可扩展。
  4. 查询自己的效率也有可能会有所提高。例如上面用IN()代替关联查询比随机的关联更加高效。

优化MIN()和MAX()

添加索引能够优化MIN()和MAX()表达式。例如,要找到某一列的最小值,只须要查询对应B-Tree索引的最左端的记录便可。相似的,若是要查询列中的最大值,也只须要读取B-Tree索引的最后一条记录。对于这种查询,EXPLAIN中能够看到"Select tables optimized away",表示优化器已经从执行计划中移除了该表,并以一个常数取而代之。服务器

用IN()取代OR

在MySql中,IN()先将本身列表中的数据进行排序,而后经过二分查找的方式肯定列的值是否在IN()的列表中,这个时间复杂度是O(logn)。若是换成OR操做,则时间复杂度是O(n)。因此,对于IN()的列表中有大量取值的时候,用IN()替换OR操做将会更快。网络

优化关联查询

在MySql中,任何一个查询均可以当作是一个关联查询,即便只有一个表的查询也是如此。
MySql对任何关联都执行嵌套循环的关联操做,例如对于下面的SQL语句:post

SELECT tbl1.col1,tbl2.col2
FROM tbl1 INNER JOIN tbl2 USING(col3)
WHERE tbl1.col1 IN(5,6);

下面的伪代码表示MySql将如何执行这个查询:性能

//先从第一个表中取出符合条件的全部行
out_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = out_iter.next
//在while循环中遍历第一个表结果集的每一行
while outer_row
    //对于第一个表结果集中的每一行,在第二个表中找出符合条件的全部行
    inner_iter = iterator over tbl2 where col3 = outer_row.col3
    inner_row = inner_iter.next
    while inner_row
        //将第一个表的结果列和第二个表的结果列拼装在一块儿做为结果输出
        output[outer_row.col1, inner_row.col2]
        inner_row = inner_iter.next
    end
    //回溯,再根据第一个表结果集的下一行,继续上面的过程
    outer_row = outer_iter.next
end

对于单表查询,那么只须要完成上面外层的基本操做。
优化关联查询,要确保ON或者USING子句中的列上有索引,而且在创建索引时须要考虑到关联的顺序。一般来讲,只须要在关联顺序中的第二个表的相应列上建立索引。例如,当表A和表B用列c关联的时候,假设关联的顺序是B、A,那么就不须要在B表的c列上创建索引。没有用到的索引只会带来额外的负担。
此外,确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样才能使用索引来优化这个过程。优化

临时表的概念

上面提到在MySql中,任何一个查询实质上都是一个关联查询。那么对于子查询或UNION查询是如何实现关联操做的呢。
对于UNION查询,MySql先将每个单表查询结果放到一个临时表中,而后再从新读出临时表数据来完成UNION查询。MySql读取结果临时表和普通表同样,也是采用的关联方式。
当遇到子查询时,先执行子查询并将结果放到一个临时表中,而后再将这个临时表当作一个普通表对待。
MySql的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候须要注意这一点。
临时表也叫派生表。

排序优化

应该尽可能让MySql使用索引进行排序。当不能使用索引生成排序结果的时候,MySql须要本身进行排序。若是数据量小于“排序缓冲区”的大小,则MySql使用内存进行“快速排序”操做。若是数据量太大超过“排序缓冲区”的大小,那么MySql只能采用文件排序,而文件排序的算法很是复杂,会消耗不少资源。
不管如何排序都是一个成本很高的操做,因此从性能角度考虑,应尽量避免排序。因此让MySql根据索引构造排序结果很是的重要。

子查询优化

MySql的子查询实现的很是糟糕。最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。
应该尽量用关联替换子查询,能够提升查询效率。

优化COUNT()查询

COUNT()有两个不一样的做用:

  1. 统计某个列值的数量,即统计某列值不为NULL的个数。
  2. 统计行数。

当使用COUNT(*)时,统计的是行数,它会忽略全部的列而直接统计全部的行数。而在括号中指定了一个列的话,则统计的是这个列上值不为NULL的个数。
能够考虑使用索引覆盖扫描或增长汇总表对COUNT()进行优化。

优化LIMIT分页

处理分页会使用到LIMIT,当翻页到很是靠后的页面的时候,偏移量会很是大,这时LIMIT的效率会很是差。例如对于LIMIT 10000,20这样的查询,MySql须要查询10020条记录,将前面10000条记录抛弃,只返回最后的20条。这样的代价很是高,若是全部的页面被访问的频率都相同,那么这样的查询平均须要访问半个表的数据。
优化此类分页查询的一个最简单的办法就是尽量地使用索引覆盖扫描,而不是查询全部的列。而后根据须要与原表作一次关联操做返回所需的列。对于偏移量很大的时候,这样的效率会提高很是大。考虑下面的查询:

SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;

若是这个表很是大,那么这个查询最好改写成下面的这样子:

SELECT film.film_id, film.description FROM sakila.film
INNER JOIN 
(SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5) AS lim
USING(film_id);

注意优化中关联的子查询,由于只查询film_id一个列,数据量小,使得一个内存页能够容纳更多的数据,这让MySQL扫描尽量少的页面。在获取到所须要的全部行以后再与原表进行关联以得到须要的所有列。
LIMIT的优化问题,实际上是OFFSET的问题,它会致使MySql扫描大量不须要的行而后再抛弃掉。能够借助书签的思想记录上次取数据的位置,那么下次就能够直接从该书签记录的位置开始扫描,这样就避免了使用OFFSET。能够把主键当作书签使用,例以下面的查询:

SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;

假设上面的查询返回的是主键为16049到16030的租借记录,那么下一页查询就能够直接从16030这个点开始:

SELECT * FROM sakila.rental WHERE rental_id < 16030
ORDER BY rental_id DESC LIMIT 20;

该技术的好处是不管翻页到多么后面,其性能都会很好。
此外,也能够用关联到一个冗余表的方式提升LIMIT的性能,冗余表只包含主键列和须要作排序的数据列。

优化UNION查询

除非确实须要服务器消除重复的行,不然必定要使用UNION ALL。若是没有ALL关键字,MySql会给临时表加上DISTINCT选项,这会致使对整个临时表的数据作惟一性检查。这样作的代价很是高。