简体   繁体   English

为什么MySQL查询需要1毫秒到7秒钟的时间?

[英]Why does a MySQL query take anywhere from 1 millisecond to 7 seconds?

I have an SQL query(see below) that returns exactly what I need but when ran through phpMyAdmin takes anywhere from 0.0009 seconds to 0.1149 seconds and occasionally all the way up to 7.4983 seconds. 我有一个SQL查询(请参阅下文),该查询完全返回我需要的内容,但通过phpMyAdmin运行时,则需要花费0.0009秒至0.1149秒,偶尔甚至达到7.4983秒。

Query: 查询:

SELECT
  e.id,
  e.title,
  e.special_flag,
  CASE WHEN a.date >= '2013-03-29' THEN a.date ELSE '9999-99-99' END as date
  CASE WHEN a.date >= '2013-03-29' THEN a.time ELSE '99-99-99' END as time,
  cat.lastname,
  FROM e_table as e
  LEFT JOIN a_table as a ON (a.e_id=e.id)
  LEFT JOIN c_table as c ON (e.c_id=c.id)
  LEFT JOIN cat_table as cat ON (cat.id=e.cat_id)
  LEFT JOIN m_table as m ON (cat.name=m.name AND cat.lastname=m.lastname)
  JOIN (
          SELECT DISTINCT innere.id
          FROM e_table as innere
          LEFT JOIN a_table as innera ON (innera.e_id=innere.id AND
                                          innera.date >= '2013-03-29')
          LEFT JOIN c_table as innerc ON (innere.c_id=innerc.id)
          WHERE (
                  (
                    innera.date >= '2013-03-29' AND 
                    innera.flag_two=1
                  ) OR 
                  innere.special_flag=1
                ) AND
                innere.flag_three=1 AND 
                innere.flag_four=1
          ORDER BY COALESCE(innera.date, '9999-99-99') ASC,
                   innera.time ASC,
                   innere.id DESC LIMIT 0, 10
       ) AS elist ON (e.id=elist.id)
  WHERE (a.flag_two=1 OR e.special_flag) AND e.flag_three=1 AND e.flag_four=1
  ORDER BY a.date ASC, a.time ASC, e.id DESC

Explain Plan: 说明计划: 以上查询说明计划

The question is: Which part of this query could be causing the wide range of difference in performance? 问题是:此查询的哪一部分可能导致性能差异很大?

To specifically answer your question: it's not a specific part of the query that's causing the wide range of performance. 要专门回答您的问题:不是查询的特定部分会导致广泛的性能。 That's MySQL doing what it's supposed to do - being a Relational Database Management System (RDBMS), not just a dumb SQL wrapper around comma separated files. 这就是MySQL在做应做的事情-作为关系数据库管理系统(RDBMS),而不仅仅是围绕逗号分隔文件的愚蠢的SQL包装器。

When you execute a query, the following things happen: 执行查询时,会发生以下情况:

  1. The query is compiled to a 'parametrized' query, eliminating all variables down to the pure structural SQL. 该查询被编译为“参数化”查询,从而消除了所有变量直至纯结构SQL。
  2. The compilation cache is checked to find whether a recent usable execution plan is found for the query. 检查编译缓存以查找是否为查询找到了最近可用的执行计划。
  3. The query is compiled into an execution plan if needed (this is what the 'EXPLAIN' shows) 如果需要,该查询将编译为执行计划(这是“ EXPLAIN”显示的内容)
  4. For each execution plan element, the memory caches are checked whether they contain fresh and usable data, otherwise the intermediate data is assembled from master table data. 对于每个执行计划元素,将检查内存高速缓存是否包含新的可用数据,否则,从主表数据中组装中间数据。
  5. The final result is assembled by putting all the intermediate data together. 通过将所有中间数据放在一起,可以得到最终结果。

What you are seeing is that when the query costs 0.0009 seconds, the cache was fresh enough to supply all data together, and when it peaks at 7.5 seconds either something was changed in the queried tables, or other queries 'pushed' the in-memory cache data out, or the DBMS has other reasons to suspect it needs to recompile the query or fetch all data again. 您所看到的是,当查询花费0.0009秒时,缓存足够新鲜,可以一起提供所有数据,当它在7.5秒达到峰值时,查询表中的内容已更改,或者其他查询“推送”了内存中缓存数据,或者DBMS有其他原因怀疑它需要重新编译查询或再次获取所有数据。 Probably some of the other variations have to do with used indexes still being cached freshly enough in memory or not. 可能其他一些变化与使用过的索引是否仍足够新鲜地缓存在内存中有关。

Concluding this, the query is ridiculously slow, you're just sometimes lucky that caching makes it appear fast. 总结一下,查询速度非常慢,您有时会很幸运,因为缓存使查询速度很快。

To solve this, I'd recommend looking into 2 things: 为了解决这个问题,我建议您研究以下两点:

  1. First and foremost - a query this size should not have a single line in its execution plan reading "No possible keys". 首先-此大小的查询在其执行计划中不应显示“没有可能的键”的任何一行。 Research how indexes work, make sure you realize the impact of MySQL's limitation of using a single index per joined table, and tweak your database so that each line of the plan has an entry under 'key'. 研究索引的工作方式,确保您了解MySQL对每个连接表使用单个索引的局限性的影响,并调整数据库,以便计划的每一行在“键”下都有一个条目。
  2. Secondly, review the query in itself. 其次,检查查询本身。 DBMS's are at their fastest when all they have to do is combine raw data. 当DBMS要做的就是合并原始数据时,它们的速度最快。 Using programmatic elements like CASE and COALESCE are by all means often useful, but they do force the database to evaluate more things at runtime than just take raw table data. 使用CASECOALESCE类的编程元素通常总是有用的,但是它们确实迫使数据库在运行时评估更多的东西,而不仅仅是获取原始表数据。 Try to eliminate such statements, or move them to the business logic as post-processing with the retrieved data. 尝试消除此类语句,或将它们移至业务逻辑,以对检索到的数据进行后处理。

Finally, never forget that MySQL is actually a rather stupid DBMS. 最后,永远不要忘记MySQL实际上是一个相当愚蠢的DBMS。 It is optimized for performance in simple data fetching queries such as most websites require. 它针对大多数网站要求的简单数据获取查询的性能进行了优化。 As such it is much faster than SQL Server and Oracle for most generic problems. 因此,对于大多数一般性问题,它比SQL Server和Oracle快得多。 Once you start complicating things with functions, cases, huge join or matching conditions etc., the competitors are frequently much better optimized, and have better optimization in their query compilers. 一旦您开始使用功能,案例,庞大的联接或匹配条件等使事情复杂化,竞争对手通常会得到更好的优化,并且其查询编译器也具有更好的优化。 As such, when MySQL starts becoming slow in a specific query, consider splitting it up in 2 or more smaller queries just so it doesn't become confused, and do some postprocessing in PHP or whatever language you are calling with. 因此,当MySQL在特定查询中开始变慢时,请考虑将其拆分为2个或更多个较小的查询,以免混淆,并使用PHP或您使用的任何语言进行一些后处理。 I've seen many cases where this increased performance a LOT, just by not confusing MySQL, especially in cases where subqueries were involved (as in your case). 我已经看到很多情况下通过不混淆MySQL而提高了性能,尤其是在涉及子查询的情况下(如您的情况)。 Especially the fact that your subquery is a derived table, and not just a subquery, is known to complicate stuff for MySQL beyond what it can cope with. 尤其是您的子查询是一个派生表,而不仅仅是一个子查询,这一事实众所周知,它使MySQL的处理变得更加复杂。

Lets start that both your outer and inner query are working with the "e" table WITH a minimum requirement of flag_three = 1 AND flag_four = 1 (regardless of your inner query's (( x and y ) or z) condition. Also, your outer WHERE clause has explicit reference to the a.Flag_two, but no NULL which forces your LEFT JOIN to actually become an (INNER) JOIN. Also, it appears every "e" record MUST have a category as you are looking for the "cat.lastname" and no coalesce() if none found. This makes sense at it appears to be a "lookup" table reference. As for the "m_table" and "c_table", you are not getting or doing anything with it, so they can be removed completely. 首先,您的外部查询和内部查询都在使用“ e”表,且最低要求为flag_three = 1和flag_four = 1(无论内部查询的((x和y)或z)条件如何。 WHERE子句具有对a.Flag_two的明确引用,但没有NULL会迫使您的LEFT JOIN实际变为(INNER)JOIN。此外,当您查找“ cat”时,似乎每个“ e”记录都必须具有一个类别。 “ lastname”,如果没有找到,则没有coalesce()。这似乎是对“ lookup”表的引用。对于“ m_table”和“ c_table”,您没有使用它或对其进行任何操作,因此它们可以完全删除。

Would the following query get you the same results? 以下查询会为您带来相同的结果吗?

select 
      e1.id,
      e1.Title,
      e1.Special_Flag,
      e1.cat_id,
      coalesce( a1.date, '9999-99-99' ) ADate,
      coalesce( a1.time, '99-99-99' ) ATime
      cat.LastName
   from
      e_table e1
         LEFT JOIN a_table as a1
             ON e1.id = a1.e_id
            AND a1.flag_two = 1
            AND a1.date >= '2013-03-29'

         JOIN cat_table as cat 
             ON e1.cat_id = cat.id
   where
          e1.flag_three = 1
      and e1.flag_four = 1 
      and (   e1.special_flag = 1
           OR a1.id IS NOT NULL )
   order by
      IF( a1.id is null, 2, 1 ),
      ADate,
      ATime,
      e1.ID Desc
   limit
      0, 10

The Main WHERE clause qualifies for ONLY those that have the "three and four" flags set to 1 PLUS EITHER the ( special flag exists OR there is a valid "a" record that is on/after the given date in question). Main WHERE子句仅适用于那些将“三个和四个”标志设置为1 PLUS的条件(存在特殊标志或在给定日期或之后存在有效的“ a”记录)。

From that, simple order by and limit. 由此可见,简单的订购受到了限制。

As for getting the date and time, it appears that you only want records on/after the date to be included, otherwise ignore them (such as they are old and not applicable, you don't want to see them). 至于获取日期和时间,您似乎只希望包括该日期/之后的记录,否则将其忽略(例如,它们较旧且不适用,您不想看到它们)。

The order by, I am testing FIRST for a NULL value for the "a" ID. 按照此顺序,我正在测试FIRST以获取“ a” ID的NULL值。 If so, we know they will all be forced to a date of '9999-99-99' and time of '99-99-99' and want them pushed to the bottom (hence 2), otherwise, there IS an "a" record and you want those first (hence 1). 如果是这样,我们知道他们都将被迫输入为“ 9999-99-99”的日期和“ 99-99-99”的时间,并希望它们被推到最底端(因此2),否则,将出现“ a ”记录下来,然后您要优先选择这些(因此1)。 Then, sort by the date/time respectively and then the ID descending in case many within the same date/time. 然后,分别按日期/时间排序,如果ID在同一日期/时间内有许多降序,则ID降序。

Finally, to help on the indexes, I would ensure your "e" table has an index on 最后,为了帮助索引,我将确保您的“ e”表在以下位置具有索引

( id, flag_three, flag_four, special_flag ).

For the "a" table, index on 对于“ a”表,在

(e_id, flag_two, date)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM