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

时间:2013-03-29 13:41:21

标签: mysql sql

我有一个SQL查询(见下文),它完全返回我需要的内容但是当通过phpMyAdmin运行时需要从0.0009秒到0.1149秒,偶尔会一直到7.4983秒。

查询:

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

解释计划: The above query explain plan

问题是: 该查询的哪一部分可能导致性能差异很大?

2 个答案:

答案 0 :(得分:4)

专门回答您的问题:这不是导致广泛性能的查询的特定部分。这是MySQL做它应该做的事情 - 作为一个关系数据库管理系统(RDBMS),而不仅仅是一个围绕逗号分隔文件的愚蠢的SQL包装。

执行查询时,会发生以下情况:

  1. 将查询编译为“参数化”查询,将所有变量排除在纯结构SQL之外。
  2. 检查编译缓存以查找是否为查询找到了最近的可用执行计划。
  3. 如果需要,查询将编译为执行计划(这就是'EXPLAIN'显示的内容)
  4. 对于每个执行计划元素,检查内存缓存是否包含新的和可用的数据,否则中间数据将从主表数据汇编。
  5. 通过将所有中间数据放在一起来组装最终结果。
  6. 您所看到的是,当查询成本为0.0009秒时,缓存足够新鲜,可以将所有数据一起提供,当它在7.5秒达到峰值时,查询表中的某些内容发生了变化,或者其他查询被“推”了内存缓存数据输出,或DBMS有其他原因怀疑它需要重新编译查询或再次获取所有数据。可能其他一些变体与使用过的索引有关,这些索引仍然在内存中足够新鲜地缓存。

    总结一下,查询速度非常慢,你有时候很幸运,缓存让它看起来很快。

    要解决这个问题,我建议您研究两件事:

    1. 首先 - 这个大小的查询在其执行计划中不应该有一行读取“No possible keys”。研究索引是如何工作的,确保你意识到MySQL对每个连接表使用单个索引的限制的影响,并调整你的数据库,以便计划的每一行都有一个条目在'key'下。
    2. 其次,查看查询本身。当他们所要做的就是结合原始数据时,DBMS是最快的。使用CASECOALESCE等编程元素通常都很有用,但是它们会强制数据库在运行时评估更多内容,而不仅仅是获取原始表数据。尝试消除此类语句,或将其作为使用检索到的数据进行后处理的业务逻辑。
    3. 最后,永远不要忘记MySQL实际上是一个相当愚蠢的DBMS。它针对大多数网站需要的简单数据提取查询进行了性能优化。因此,对于大多数通用问题,它比SQL Server和Oracle快得多。一旦您开始使用函数,大小写,大型连接或匹配条件等复杂化,竞争对手通常会更好地进行优化,并在查询编译器中进行更好的优化。因此,当MySQL在特定查询中开始变慢时,请考虑将其拆分为2个或更多个较小的查询,以免它变得混乱,并在PHP或您正在调用的任何语言中进行一些后处理。我已经看到很多情况下这种性能提升很多,只是没有混淆MySQL,特别是在涉及子查询的情况下(如你的情况)。特别是你的子查询是一个派生表,而不仅仅是一个子查询这一事实,众所周知,它可以使MySQL的内容复杂化,超出它的应对范围。

答案 1 :(得分:1)

让我们开始你的外部和内部查询都在使用" e" table最低要求为flag_three = 1 AND flag_four = 1(无论你的内部查询是什么((x和y)或z)条件。另外,你的外部WHERE子句明确引用了a.Flag_two,但是没有NULL会强制你的LEFT JOIN实际上成为一个(内部)JOIN。而且,每个" e"记录必须有一个类别,因为你正在寻找" cat.lastname"没有coalesce(),如果没有找到。这是有道理的,它似乎是一个"查找"表引用。至于" m_table"和" c_table",你没有得到或做任何事情,所以他们可以完全删除。

以下查询会得到相同的结果吗?

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

Main WHERE子句仅限于那些拥有"三和四"标志设置为1加上(特殊标志存在或者有效的" a"记录在给定日期之上/之后)。

从那个,简单的顺序和限制。

至于获取日期和时间,您似乎只想要包含日期/日期后的记录,否则忽略它们(例如它们陈旧且不适用,您不想看到它们)。

顺序依次为,我正在测试FIRST的" a" ID。如果是这样,我们知道他们都将被迫到#9; 9999-99-99'时间和99-99-99'并希望他们被推到底部(因此2),否则,有一个" a"记录,你想要那些(因此1)。然后,分别按日期/时间排序,然后在相同日期/时间内的许多情况下降序ID。

最后,为了帮助索引,我会确保你的" e"表上有一个索引

( id, flag_three, flag_four, special_flag ).

对于" a"表,索引

(e_id, flag_two, date)