高性能Mysql-查询性能优化

1、为什么查询会变慢

在尝试编写快速的查询之前,首先我们需要明白,真正重要的是响应时间。如果把查询看做是一个任务,那么他就是由多个子任务组成的,优化查询也就是需要优化这些子任务。通常来说,查询的生命周期大致可以按照顺序来看:从客户端到服务器,然后再服务器上进行解析,生成执行计划,执行,并返回结果给客户端。在完成这些任务的时候,查询需要在不同的地方花费时间,包括网络,CPU计算,生成统计信息和执行计划,锁等待等操作。尤其是向底层存储引擎检索数据的调用操作,这些调用需要在内存操作,CPU操作,和内存不足时导致的I/O操作上耗费时间。

2、慢查询基础:优化数据访问

查询性能低下最基本的原因是访问的数据太多。某些查询可能不可避免地需要筛选大量 数据, 但这并不常见。大部分性能低下的查询都可以通过械少访问的数据量的方式进行 优化。对于低效的查询, 我们发现通过下面两个步骤来分析总是很有效:

①    确认应用程序是否在检索大量超过需要的数据。这通常意味着访问了太多的行, 但有时候也可能是访问了太多的列。
②   确认MySQL服务器层是否在分析大量超过需要的数据行。

2.1 是否向数据库请求了不需要的数据

有些查询会请求超过实际需要的数据, 然后这些多余的数据会被应用程序丢弃。这会给MySQL服务器带来额外的负担, 井增加网络开销,另外也会消耗应用服务器的CPU 和内存资源。这里有一些典型案例:

① 查询不需要的记录:一个常见的错误是常常会误以为MySQL会只返回需要的数据, 实际上MySQL却是先返回全部结果集再进行计算。

② 多表关联时返回全部列。

③ 总是取出全部列:每次看到 SELECT *的时候都需要用怀疑的眼光审视, 是不是真的需要返回全部的列?很可能不是必需的。 取出全部列, 会让优化器无怯完成索引覆盖扫描这类优化,还会为服务器带来额外的I/O、 内存和CPU的消耗。

④ 重复查询相同的数据。

2.2 MySQL是否在扫描额外的记录

在确定查询只返回需要的数据以 后, 接下来应该看看查询为了返回结果是否扫描了过多 的数据。对于MySQL, 最简单的衡量查询开销的三个指标如下:晌应时间、扫描的行数、返回的行数。

响应时间:响应时间是两个部分之和:服务时间和排队时间。 服务时间是指数据库处理这个查询真正花了多长时间。 排队时间是指服务器因为等待某些资源而没有真正执行查询的时间一一可能是等I/O操作完成, 也可能是等待行锁, 等等。

扫描的行数和返回的行数:理想情况下扫描的行数和返回的行数应该是相同的,但在实际情况中,当我们在做 关联查询时,服务器必须要扫描多行才能生成结果集的一行。

扫描的行数和访问类型:在评估查询开销时,需要考虑一下从表中找到某一行数据的成本。Mysql有好几种访问方式可以查找并返回一行结果。有些访问方式需要扫描很多行才会返回一行。在explain语句中,type列反映了访问类型。如果查询没有找到合适分访问类型,那么解决的最好办法就是添加索引。

一般mysql能够使用如下三种方式应用WHERE条件,从好带坏一次为:

•     在索引中使用WHERE条件来过滤不匹配的记录。 这是在存储引擎层完成的。
•     使用索引覆盖扫描(在Extra列中出现了Using index) 来返回记录, 直接从索引中过滤不需要的记录并返回命中的结果。 这是在MySQL服务器层完成的, 但无须再回表查询记录。
•     从数据表中返回数据,然后过施不满足条件的记录(在Extra列中出现Using Where)。这在MySQL服务器层完成,MySQL需要先从数据表读出记录然后过滤。

3、重构查询方式

有时候, 可以将查询转换一种写法让 其返回一样的结果,但是性能更好。但也可以通过修改应用代码,用另一种方式完成查询,最终达到一样的目的。

3.1 一个复杂查询还是多个简单查询

MySQL从设计上让连接和断开连接都很轻量级,再返回一个小的查询结果方面很高效。不过, 在应用设计的时候, 如果一个查询能够胜任时还写成多个独立查询是不明智的。

3.2 切分查询

有时候对于一个大查询我们需要 “分而治之”,将大查询切分成小查询, 每个查询功能完全一样, 只完成一小部分, 每次只返回一小部分查询结果。删除旧的数据就是一个很好的例子。 定期地清除大量数据时, 如果用一个大的语句一次性完成的话, 则可能需要一次锁住很多数据、 占满整个事务日志、 艳尽系统资源、 阻塞 很多小的但重要的查询。 将一个大的 DEL盯E语句切分成多个较小的查询可以尽可能小地 影响MySQL性能, 同时还可以减少MySQL复制的延迟。

3.3 分解关联查询

很多高性能的应用都会对关联查询进行分解。 简单地, 可以对每一个表进行一次单表查询, 然后将结果在应用程序中进行关联。 用分解关联查询的方式重构查询有如下 的优势:

•    让缓存的效率更高。 许多应用程序可以方便地缓存单表查询对应的结果对象。 
•    将查询分解后,执行单个查询可以减少锁的竞争。
•    在应用层做关联, 可以更容易对数据库进行拆分, 更容易做到高性能和可扩展。
•    查询本身效率也可能会有所提升。 
•    可以减少冗余记录的查询。 在应用层做关联查询, 意味着对于某条记录应用只需要查询一次,而在数据库中做关联查询,则可能需要重复地访问一部分数据。 从这点看, 这样的重构还可能会减少网络和内存的消耗。
•    更进一步, 这样做相当于在应用中实现了哈希关联, 而不是使用MySQL的嵌套循环关联。 

4、查询执行的基础

当希望MySQL能够以更高的性能运行查询时, 最好的办法就是弄清楚MySQL是优化和执行查询的。如下图:

1.    客户端发送一条查询给服务器。
2.    服务器先检查查询缓存, 如果命中了缓存, 则立刻返回存储在缓存中的结果。 否则进入下一阶段。
3.    服务器端进行SQL解析、 预处理,再由优化器生成对应的执行计划。
4.    MySQL根据优化器生成的执行计划, 调用存储引擎的API来执行查询。
5.    将结果返回给客户端。

5、mysql查询优化器的局限性

MySQL的万能 “嵌套循环” 并不是对每种查询都是最优的。 不过还好,MySQL查询优化器只对少部分查询不适用, 而且我们往往可以通过改写查询让MySQL高效地完成工 作。

5.1 关联子查询

MySQL的子查询实现得非常糟腾。 最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。那如何用好关联子查询呢?

例如:当返回结果中只有一个表的某些列的时候。但是具体情况还需要我们在测试完成后选择具体的解决方案。

5.2 union的限制

有时,MySQL无法将限制条件从外层“下推”到内层,这使得原本能够限制部分返回结果的条件无怯应用到内层查询的优化上。
如果希望UNION的各个子句能够根据LIMIT只取部分结果集,或者希望能够先排好序再合并结果集的话,就需要在UNION的各个子句中分别使用这些子句。例如:两个表合并去20条数据,可以每个表都limit20条,在合并。而不是先合并,在limit20。

5.3 索引合并优化

当WHERE子句中包含多个复杂条件的 时候,MySQL能够访问单个表的多个索引以合井和交叉过滤的方式来定位需要查找的行。

5.4 等值传递

某些时候,等值传递会带来一些意想不到的额外消耗。 例如,有一个非常大的IN()列表,而MySQL优化器发现存在WHERE、 ON或者USING的子句, 将这个列表的值和另一个表的某个列相关联。那么优化器会将IN()列表都复制应用到关联的各个表中。 

5.5 并行执行

MySQL无法利用多核特性来并行执行查询。 很多其他的关系型数据库能够提供这个特性,但是MySQL做不到。 

5.6 哈希关联

MySQL的所有关联都是嵌套循环关联。 不过, 可以通过建立一个哈希索引来曲线地实现哈希关联。如果使用的是Memory存储引擎, 则索引都是哈希索引, 所以关联的时候也类似于哈希关联。

5.7 松散索引扫描

由于历史原因,MySQL并不支持松散索引扫描, 也就无法按照不连续的方式扫描一个索引。 通常,MySQL的索引扫描需要先定义一个起点和终点, 即使需要的数据只是这段索引中很少数的几个,MySQL仍需要扫描这段索引中每一个条目。

5.8 最大值和最小值优化

对于MIN() 和MAX() 查询,MySQL的优化做得并不好。

5.9 在同一个表上查询和更新

mysql不允许对同一张表进行同时更新和查询。

6、优化特定类型的查询

6.1 优化count()查询

COUNT ()是一个特殊的函数, 有两种非常不同的作用,它可以统计某个列值的数量, 也可以统计行数。在统计列值时要求列值是非空的(不统计 NULL)。 如果在 COUNT()的括号中指定了列或者列的表达式, 则统计的就是这个表达式有值的结果数 。

简单的优化:有时候可以使用 MyISAM 在 COUNT(*)全表非常快的这个特性, 来加速一些特定条件的 COUNT()的查询。

使用近似值:有时候某些业务场景并不要求完全精确的 cou阳值,此时 可以用近似值来代替。

更复杂的优化:除了前面的方法,在MySQL层面还能做的就只有索引覆盖扫描了。

6.2 优化关联查询

•    确保ON或者 USING子句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。当表A 和表B用列c关联的时候, 如果优化器的关联顺序是B、A, 那么就不需要在B表的对应列上建上索引。 没有用到的索引只会带来额外的负担。 一般来说, 除非 有其他理由, 否则只需要在关联顺序中的第二个表的相应列上创建索引。
•    确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列, 这样MySQL才有可能使用索引来优化这个过程。
•    当升级MySQL的时候需要注意:关联语法、 运算符优先级等其他可能会发生变化 的地方。 因为以前是普通关联的地方可能会变成笛卡儿积, 不同类型的关联可能会 生成不同的结果等。

6.3 优化子查询

关于子查询优化我们给出的最重要的优化建议就是尽可能使用关联查询代替, 至少当前的MySQL版本需要这样。 

6.4 优化GROUP BY和DISTINCT

在很多场景下, MySQL都使用同样的办法优化这两种查询, 事实上, MySQL优化器会在内部处理的时候相互转化这两类查询。 它们都可以使用索引来优化, 这也是最有效的优化办法。

在MySQL中, 当无战使用索引的时候, GROUP 即使用两种策略来完成:使用临时表或者文件排序来做分组。如果需要对关联查询做分组(GROU P BY),井且是按照查找表中的某个列进行分组, 那么通常采用查找表的标识列(例如主键)分组的效率会比其他列更高。

6.5 优化LIMIT分页

在系统中需要进行分页操作的时候,我们通常会使用 LIMIT 加上偏移量的办法实现,同时加上合适的 ORDER BY 子句.如果有对应的索引,通常效率会不错,否则,MySQL 需 要做大量的文件排序操作。一个非常常见又令人头离的问题就是,在偏移量非常大的时候,例如可能是 LIMIT 1000 到20这样的查询,这时 MySQL 需要查询 10 020 条记录然后只返回最后 20 条,前面10 000 条记录都将被抛弃,这样的代价非常高。

优化此类分页查询的一个最简单的办格就是尽可能地使用索引覆盖扫描,而不是查询所 有的列。比如:SELECT film_id, description From sakila.film ORDER BY title LIMIT 50,5;此查询可以修改为:SELECT film.film_id, film.description FROM sakila.film INNER JON(SELECT film.id FROM sakila.film ORDER BY title LIMIT 50,5 ) AS lim USING(film_id); 这里的延迟关联将大大提升效率。

6.6 优化SQL_CALC_FOUND_ROWS

分页的时候,另一个常用的技巧是在LIMIT语句中加上SOL_ CALC_ FOUND_ ROWS提示(hint),这样就可以获得去掉 LIMIT以后满足条件的行数,因此可以作为分页的总数。

6.7 优化UNION查询


除非确实需要服务器消除重复的行,否则就 定要使用UNION ALL,这 一点很重要。如果没有ALL关键字,MySQL会给临时表加上 DISTINCT选项,这会导致对整个临时表的数据唯一性检查。
 

猜你喜欢

转载自blog.csdn.net/dongzl0230/article/details/83539093