3

我有一个 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

解释计划: 上述查询说明计划

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

4

2 回答 2

6

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

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

  1. 查询被编译为“参数化”查询,消除了所有变量到纯结构化 SQL。
  2. 检查编译缓存以查找是否为查询找到了最近可用的执行计划。
  3. 如果需要,查询会编译成执行计划(这就是“解释”显示的内容)
  4. 对于每个执行计划元素,检查内存缓存是否包含新鲜和可用的数据,否则中间数据是从主表数据组装而成的。
  5. 通过将所有中间数据放在一起来组装最终结果。

您所看到的是,当查询花费 0.0009 秒时,缓存足够新鲜,可以同时提供所有数据,而当它在 7.5 秒达到峰值时,查询表中的某些内容发生了变化,或者其他查询“推送”了内存缓存数据,或者 DBMS 有其他原因怀疑它需要重新编译查询或再次获取所有数据。可能其他一些变化与使用的索引是否仍然在内存中足够新鲜地缓存有关。

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

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

  1. 首先也是最重要的 - 这种大小的查询在其执行计划中不应有一行读取“没有可能的键”。研究索引是如何工作的,确保你意识到 MySQL 对每个连接表使用单个索引的限制的影响,并调整你的数据库,以便计划的每一行在“key”下都有一个条目。
  2. 其次,检查查询本身。当 DBMS 所要做的只是合并原始数据时,它们的速度是最快的。CASE使用诸如和之类的编程元素COALESCE通常很有用,但它们确实迫使数据库在运行时评估更多的东西,而不仅仅是获取原始表数据。尝试消除此类语句,或将它们移动到业务逻辑中,作为检索数据的后处理。

最后,永远不要忘记 MySQL 实际上是一个相当愚蠢的 DBMS。它针对大多数网站所需的简单数据获取查询的性能进行了优化。因此,对于大多数通用问题,它比 SQL Server 和 Oracle 快得多。一旦你开始用函数、案例、巨大的连接或匹配条件等使事情复杂化,竞争对手通常会得到更好的优化,并且在他们的查询编译器中也有更好的优化。因此,当 MySQL 在特定查询中开始变慢时,请考虑将其拆分为 2 个或更多较小的查询,以免混淆,并在 PHP 或您调用的任何语言中进行一些后处理。我已经看到很多情况下,这会大大提高性能,只是不要混淆 MySQL,尤其是在涉及子查询的情况下(如您的情况)。

于 2013-04-17T16:14:23.427 回答
1

让我们开始您的外部和内部查询都使用“e”表,最低要求为 flag_three = 1 AND flag_four = 1(无论您的内部查询的(( x 和 y )或 z)条件如何。此外,您的外部WHERE 子句明确引用了 a.Flag_two,但没有 NULL 强制您的 LEFT JOIN 实际上成为(INNER)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”记录在所讨论的给定日期/之后)。

从此,简单的排序和限制。

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

按顺序,我正在为“a”ID 的 NULL 值测试 FIRST。如果是这样,我们知道他们都将被强制设置为“9999-99-99”的日期和“99-99-99”的时间,并希望它们被推到底部(因此为 2),否则,有一个“a " 记录,你首先想要那些(因此是 1)。然后,分别按日期/时间排序,如果同一日期/时间中有多个,则 ID 降序排列。

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

( id, flag_three, flag_four, special_flag ).

对于“a”表,索引

(e_id, flag_two, date)
于 2013-04-17T19:42:05.260 回答