MySQL之查询性能优化

为什么查询速度会慢

通常来说,查询的生命周期大致可以按照顺序来看:从客户端,到服务器,然后在服务器上进行解析,生成执行计划,执行,并返回结果给客户端。其中“执行”可以认为是整个生命周期中最重要的阶段,这其中包括了大量为了检索数据到存储引擎的调用以及调用后的数据处理,包括排序、分组等。

在完成这些任务的时候,查询需要在不同的地方花费时间,包括网络,CPU计算,生成统计信息和执行计划、锁等待(互斥等待)等操作,尤其是向底层存储引擎检索数据的调用操作,这些调用需要在内存操作、CPU操作和内存不足时导致的I/O操作上消耗时间。

在每一个消耗大量时间的查询案例中,都能看到一些不必要的额外操作、某些操作被额外地重复了很多次、某些操作执行得太慢等。优化查询的目的就是减少和消除这些操作所花费的时间。

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

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

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

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

如果请求并不需要的数据,然后在应用层将这些程序丢弃。这会给MySQL服务器带来额外的负担,并增加网络开销,另外也会消耗应用服务器的CPU内存和资源。

这里有一些典型案例:

查询不需要的记录

比如数据库中存有一百条新闻记录,读取所有的新闻记录,却只显示前十条,这种情况比较好处理,加上limit即可。

单表查询或多表关联查询时返回全部的列

在设计表的时候,并非所有的字段都是用于前端展示,比方你会设计一个时间戳字段,用于记录某条数据的插入时间或更新时间,但前端对这个字段并不关心,而在单表查询或多表关联查询时,为图方便,直接用"SELECT *"返回所有的字段,这会让优化器无法完成索引覆盖这类优化,还会为服务器带来额外的IO、内存和CPU的消耗。当然,在数据量并不大、访问量不大的情况下,可以使用"SELECT *"简化开发。

重复查询相同的数据

我曾经开发过一个复杂的模块,里面有不少数据需要从数据库查询,如果用面向过程的方式来编程,需要传递非常多的参数,其中也包括从数据库中查询的对象,但是如果不传递那些对象,在别的调用方法中,又需要重新从数据库中读取数据。

MySQL是否在扫描额外的记录

在确定查询只返回所需要的列后,接下来便是看看查询是否扫描过多的数据。对于MySQL,最简单的衡量查询开销的三个指标如下:

  • 响应时间。
  • 扫描的行数。
  • 返回的行数。

没有哪个指标能够完美地衡量查询的开销,但它们大致反映了MySQL在内部执行查询时需要多少数据,并可以推算出查询运行的时间。这三个指标都会记录到MySQL的慢日志中,所以检查慢日志记录是找出扫描行数过多的查询的好办法。

响应时间

响应时间是两个部分之和:服务时间和排队时间。服务时间是指数据库处理这个查询真正花了多少时间。排队时间是指服务器应为等待某些资源而没有真正执行查询的时间——可能是等待IO操作完成,也可能是等待行锁,等等。遗憾的是,我们无法把响应时间细分到上面这些部分,除非有什么办法能够逐个测量上面这些消耗,不过很难做到。一般最常见和最重要的等待是IO和锁等待,但是实际情况更加复杂。

所以在不同类型的应用压力下,响应时间并没有什么一致的规律或者公式。诸如存储引擎的锁(表锁、行锁)、高并发资源竞争、硬件响应等诸多因素都会影响响应时间。所以,响应时间既可能是一个问题的结果也可能是一个问题的原因。

扫描的行数和返回的行数

分析查询时,查看该查询扫描的行数是非常有帮助的。这在一定程度上能够说明该查询找到需要数据的效率高不高。理想情况下扫描的行数和返回的行数应该是相同的。但是实际情况中这种“美事”并不多。例如在做一个关联查询时,服务器必须要扫描多行才能生成结果集中的一行。扫描的行数对返回的行数的比率通常很小,一般在1:1和10:1之间,不过有时候这个值也可能非常大。

扫描的行数和访问类型

在评估查询开销的时候,需要考虑一下从表中找到某一行数据的成本。mysql有好几种访问方式可以查找并返回一行结果。有些访问方式可以需要扫描很多行才能返回一行结果,也有些访问方式可以无需扫描就能返回结果。

在EXPLAIN语句的type列反应了访问类型。访问类型有很多种,从全表扫描到索引扫描、范围扫描。唯一索引查询、常数引用等。这里列的这些,速度是从慢到快,扫描的行数也是从小到大。你不需要记住这些访问类型,但要明白扫描表、扫描索引、范围访问和单值访问的概念。

如果查询没有办法找到合适的访问类型,那么最好的解决方法通常就是增加一个合适的索引,索引让MySQL以最高效,扫描行数最少的方式找到需要的记录。

例如,我们看看示例数据库Sakila中的一个查询案例:

SELECT * FROM film_actor WHERE film_id = 1; 

  

这个查询返回十行数据,从EXPLAIN的结果可以看到,MySQL的索引idx_fk_film_id上使用了ref访问类型来执行查询:

mysql> EXPLAIN SELECT * FROM film_actor WHERE film_id = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ref
possible_keys: idx_fk_film_id
          key: idx_fk_film_id
      key_len: 2
          ref: const
         rows: 10
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

  

EXPLAIN的结果显示需要访问十行数据。换句话说,查询优化器认为这种访问类型可以高效的完成查询。如果没有合适的索引会怎样呢?MySQL就不得不使用一种更糟糕的访问类型,下面我们来看看如果删除对应的索引来运行这个查询:

mysql> ALTER TABLE film_actor DROP FOREIGN KEY fk_film_actor_film;
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE film_actor DROP KEY idx_fk_film_id;
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> EXPLAIN SELECT * FROM film_actor WHERE film_id = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: film_actor
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5462
     filtered: 10.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

  

访问类型变成了一个全表扫描,现在MySQL预估需要扫描5073条记录就能完成这个查询。这里的USING WHERE 表示MySQL将通过WHERE 条件来筛选存取引擎返回的记录。

一般MySQL能够使用如下三种方式使用WHERE 条件,从好到坏依次为:

  • 在索引中使用WHERE条件来过滤掉不匹配的记录。这是在存储引擎层完成的。
  • 使用索引覆盖扫描(在Extra列中出现Using index)来返回记录,直接从索引中过滤掉不需要的记录并返回命中结果。这是在MySQL服务器层完成的,但无需再回表查询记录。
  • 从数据表中返回数据,然后过滤不满足条件的记录(在Extra列中出现Using Where)。这是在MySQL服务器层完成的,MySQL需要从数据表中读出记录然后过滤。上面这个例子说明了好的索引是多么重要。好的索引可以让查询使用合适的访问类型,尽可能的只扫描需要的数据行。但也不是说增加索引就能让扫描的行数等于返回的行数。例如下面的使用聚合函数COUNT()的查询:

    SELECT actor_id ,COUNT(*) FROM film_actor GROUP BY actor_id;
    

    这个查询需要读取几千行数据,但是仅返回了200行结果。没有什么索引能够让这样的查询减少需要扫描的行数。

    不幸的是,MySQL不会告诉我们生成结果实际上需要扫描多数行数据,而只会告诉我们生成结果时一共扫描了多数行数据。扫描的函数中大部分都很可能被WHERE条件过滤掉的,对最终的结果并没有贡献。在上面的例子中,我们删除索引后,看到MySQL需要扫描索引记录然后根据WHERE条件过滤,最终返回10行结果。理解一个查询需要扫描多数行和实际需要使用的行数首先需要理解这个查询背后的逻辑和思想。

如果发现查询需要大量的数据但值返回少数行,那么通常可以尝试下面的技巧去优化它:

  • 使用索引覆盖扫描,把所有需要用的列都放到索引中,这样存储引擎无需回表获取对应的行就可以返回结果了。
  • 改变库表结构。例如使用单独的汇总表。
  • 重写这个查询,让MySQL优化器能够以更优化的方式执行这个查询。

重构查询的方式

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

设计查询的时候一个需要考虑的重要问题是,是否需要将一个复杂的查询分成多个简单的查询。在传统实现中,总是强调数据库层完成尽可能多的工作,这样做的逻辑在于以前总是认为网络通信、查询解析和优化是一件代价很高的事情。

但是这样想法对于MySQL并不适用,MySQL从设计上让链接和断开都很轻量级,返回一个小查询结果方面很高效。现代的网络速度比以前要快很多,无论是带宽还是延迟。在某些版本的MySQL上,即使在一个通用服务器上,也能够运行每秒超过10万的查询,即使在一个千兆网卡也能够轻松满足每秒超过2000次的查询。所以运行多个小的查询现在已经不是大问题了。

MySQL内部能够扫描内存中上百万数据,相比之下,MySQL响应数据给客户端就慢得多了。在其他条件都相同的时候,使用尽可能少的查询当然是更好。但是有时候,将一个大查询分解为多个肖查询是很有必要的。别害怕这样做,好好衡量一下这样做是不是会减少工作量。

切分查询

有时候对于一个大查询我们需要“分而治之”, 将大查询切分成小查询,每个查询功能完全一样,只完成一小部分,每次只返回一小部分查询结果。

删除旧数据就是一个很好的例子。定期的清楚大量数据时,如果用一个大的语句一次性完成,则可能需要一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但是很重要的查询。将一个大的DELETE语句切分成多个较小的查询可以尽可能小地影响MySQL性能,同时还可以减少MySQL复制的延迟。例如,我们需要每个月运行一次下面的查询:

mysql> DELETE FROM messages WHERE created<DATE_SUB(NOW(),INTERVAL 3 MONTH);

  

那么可以用类似下面的办法来完成工作:

rows_affected=0
do {
	rows_affected = do_query(
	"DELETE FROM messages WHERE created < DATE_SUB(NOW(),INTERVAL 3 MONTH)
	LIMIT 10000"
} where rows_affected > 0

  

一次删除一万行数据一般来说是一个比较高效而且对服务器影响也最小的做法(如果是事务型引擎,很多时候小事务能够更高效)。同时需要注意的是,如果每次删除数据后都站定一会再做一下次删除,这样可以将服务器上原本一次性的压力分散到一个很长的时间段中,就可以大大降低对服务器的影响,还可以大大减少删除时锁的持有时间。

分解关联查询

很多高性能的应用都会对关联查询进行分解,简单的对每一个表进行一次单表查询,然后将结果在应用程序中进行关联。例如,下面这个查询:

mysql> 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";

  

可以分解成下面这些查询来替代:

mysql> SELECT * FROM tag WHERE tag="mysql";
mysql> SELECT * FROM tag_post WHERE tag_id=1234;
mysql> SELECT * FROM tag_post WHERE post.id in (123,456,789,9098.8904);

  

乍一看,这样做并没有什么好处,原本一条查询,这里却变成了多条查询,返回结果又是一模一样。事实上,用分解关联查询的方式重构查询有如下的优势:

  • 让缓存的效率更高。许多应用程序可以方便的缓存单标查询对应的记过对象。例如,上面查询中的tag已经被缓存了,那么应用就可以跳过第一个查询。再例如,应用已经缓存了ID为123、456、789、9098的内容,那么第三个查询的IN()就可以少几个ID。另外对MySQL的查询缓存来说,如果关联中的某个表发生了变化,那么就无法使用查询缓存了,而拆分后,如果某个表很少改变,那么基于该表的查询就可以重复利用查询缓存结果了。
  • 将查询分解后,执行单个查询可以减少锁的竞争。
  • 在应用层做关联,可以更容易对数据库进行拆分,更容易做到高性能和可扩展性。
  • 查询本身的效率也可能会有所提升。这个例子中,使用IN()代替关联查询,可以让MySQL按照ID顺序进行查询,这可能比随机关联更高效。
  • 可以减少冗余记录的查询。在应用层做关联查询,因为这对于某条记录应用只需要查询一次,而在数据库中做关联查询,则可能需要重复地访问一部分数据。从这点看,这样的重构还可能会减少网络和内存的消耗。
  • 更进一步,这样做相当于在应用中实现了哈希关联,而不是使用MySQL的嵌套循环关联。某些场景哈希关联的效率要高很多。

在很多场景,通过重构查询将关联放到应用程序中将会更加高效,这样的场景有很多,比如:当应用能够方便地缓存单个查询的结果的时候、当可以将数据分布到不同的MySQL服务器上的时候、当能够使用IN()的方式代替关联查询的时候、当查询中使用同一个数据表的时候。

原文地址:https://www.cnblogs.com/beiluowuzheng/p/10119834.html

时间: 2024-10-08 12:32:10

MySQL之查询性能优化的相关文章

MySQL之查询性能优化四

MySQL的万能"嵌套循环"并不是对每种查询都是最优的.不过还好,mysql查询优化器只对少部分查询不适用,而且我们往往可以通过改写查询让mysql高效的完成工作.在这我们先来看看mysql优化器有哪些局限性: 1.关联子查询 mysql的子查询实现得非常糟糕.最糟糕得一类查询是where条件中包含in()的子查询语句. 例如,我们希望找到sakila数据库中,演员Penlope Guiness参演的所有影片信息. 很自然的,我们会按照下面的方式用子查询实现: select * fro

MySQL之查询性能优化一

只有当查询优化,索引优化,库表结构优化齐头并进时,才能实现mysql高性能. 在尝试编写快速的查询之前,需要清楚一点,真正重要是响应时间. 通常来说,查询的生命周期大致可以按照顺序来看:从客户端,到服务器,然后再服务器上进行解析,生成执行计划,执行,并返回结果给客户端. 其中"执行"可以认为是整个生命周期最重要的阶段,这其中包括了大量为了检索数据到存储引擎的调用以及调用后的数据处理,包括排序,分组等. 对于一个查询的全部生命周期,上面列的并不完整.这里我们只是想说:了解查询的生命周期,

MySQL之查询性能优化五(优化特定类型的查询)

本文将介绍如何优化特定类型的查询. 1.优化count()查询 count()聚合函数,以及如何优化使用了该函数的查询,很可能是mysql中最容易被误解的前10个话题之一 count() 是一个特殊的函数,有两种非常不同的作用.它可以统计某个列值的数量,也可以统计行数. 统计列值 要求列值是非空的.(不统计null,即null值计数为0) count()的另一个用处是统计结果集的行数.当mysql确认括号的表达式值不可能为空时,实际上就是统计 行数.最简单的就是当我们使用count(*)的时候,

MySQL分页查询性能优化

当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询.对于数据库分页查询,也有很多种方法和优化的点.下面简单说一下我知道的一些方法. 准备工作 为了对下面列举的一些优化进行测试,下面针对已有的一张表进行说明. 表名:order_history 描述:某个业务的订单历史表 主要字段:unsigned int id,tinyint(4) int type 字段情况:该表一共37个字段,不包含text等大型数组,最大为varcha

mysql笔记03 查询性能优化

查询性能优化 1. 为什么查询速度会慢? 1). 如果把查询看作是一个任务,那么它由一系列子任务组成,每个子任务都会消耗一定的时间.如果要优化查询,实际上要优化其子任务,要么消除其中一些子任务,要么减少子任务的执行次数,要么让子任务运行的更快. 2). 通常来说,查询的生命周期大致可以按照顺序来看:从客户端,到服务器端,然后在服务器上进行解析,生成执行计划,执行,并返回结果给客户端.其中"执行"可以认为是整个生命周期中最重要的阶段,这其中包括 大量为了检索数据到存储引擎的调用以及调用后

170727、MySQL查询性能优化

MySQL查询性能优化 MySQL查询性能的优化涉及多个方面,其中包括库表结构.建立合理的索引.设计合理的查询.库表结构包括如何设计表之间的关联.表字段的数据类型等.这需要依据具体的场景进行设计.如下我们从数据库的索引和查询语句的设计两个角度介绍如何提高MySQL查询性能. 数据库索引 索引是存储引擎中用于快速找到记录的一种数据结构.索引有多种分类方式,按照存储方式可以分为:聚簇索引和非聚簇索引:按照数据的唯一性可以分为:唯一索引和非唯一索引:按照列个数可以分为:单列索引和多列索引等.索引也有多

高性能mysql 第六章查询性能优化 总结(上)查询的执行过程

6  查询性能优化 6.1为什么查询会变慢 这里说明了的查询执行周期,从客户端到服务器端,服务器端解析,优化器生成执行计划,执行(可以细分,大体过程可以通过show profile查看),从服务器端返回客户端结果. 而执行部分作为最重要的一环,需要做的事情比较多,而不合适的query往往让执行过程做了不必要的操作,或者不能使用更优秀的底层数据结构,从而用时更久. 6.2慢查询基础:优化数据访问 访问数据量多大,超过实际所需是慢查询的一个原因.导致这种情况的原因大致有两个 1.应用程序向mysql

查询性能优化

查询性能优化 怎么样算查询性能比较好?响应时间短(获取查询数据速度快) 优化数据访问 查询性能低下最基本的原因是访问的数据太多.大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化. 对于低效的查询,我们发现通过下面两个步骤来分析总是很有效: 确认应用程序是否在检索大量超过需要的数据.这通常意味着访问了太多行,但有时候也可能是访问了太多的列. 确认MySQL服务器层是否在分析大量超过需要的数据行. 总结:1.只查询了需要的列2.在满足要求的前提下尽可能扫描少的行 是否向数据库请求了不需要

SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用

原文:SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用 近段时间以来,一直在探究SQL Server查询性能的问题,当然也漫无目的的查找了很多资料,也从网上的大神们的文章中学到了很多,在这里,向各位大神致敬.正是受大神们无私奉献精神的影响,所以小弟也作为回报,分享一下关于SET STATISTICS IO和SET STATISTICS TIME这两条T_SQL命令,在查询优化性能中的作用. 首先我想说明一下这篇文章不是关于如何