为什么WHERE语句中的子查询在MYSQL中这么慢?

时间:2019-03-20 14:18:38

标签: mysql

以下查询需要1秒钟多一点的时间才能完成,并返回24k个结果。

    SELECT DISTINCT
    (ad_name)
FROM
    marketing.fbk_ad_stats_daily AS marketing
WHERE
    date_start >= subdate(CURRENT_DATE,30)
    and spend >1

当我在以下WHERE语句中将查询作为条件使用时:

SELECT
    name,
    tracking_key
FROM
    marketing.ads
WHERE
    name IN
             (
             SELECT DISTINCT
                 (ad_name)
             FROM
                 marketing.fbk_ad_stats_daily
             WHERE
                 date_start >= subdate(CURRENT_DATE,30))

它运行了数十分钟,在看到它实际需要多长时间之前,我将其关闭。但是当我获取原始查询的结果并将其用作WHERE语句中的条件时,就像这样...

SELECT
    name,
    tracking_key
FROM
    marketing.ads
    where name 
in ('bnj-fbk-m-us-5db72043 c18 - MF-Image18-US-OS-Android',
'bnj-fbk-m-us-5db72043 c17 - MF-Image17-US-OS-Android',
'bnj-fbk-m-us-f72f73c8 c33 - MF-Image33-US-OS-Android',
'bnj-fbk-m-us-f72f73c8 c35 - MF-Image35-US-OS-Android',
'bnj-fbk-m-us-5db72043 c6 - MF-Image6-US-OS-Android', ... etc... x 24k rows... )

我得到了3秒的超快运行时间。 是什么解释了两种方法之间的区别?为什么第二个查询不是两个查询的线性组合?

第二个查询的解释是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  <subquery2> ALL (null)  (null)  (null)  (null)  (null)  (null)
1   SIMPLE  ads ALL (null)  (null)  (null)  (null)  826919  Using where; Using join buffer (Block Nested Loop)
2   MATERIALIZED    fbk_ad_stats_daily  range   ix_fbk_ad_stats_daily_date_start    ix_fbk_ad_stats_daily_date_start    6   (null)  399630  Using index condition

第三个查询的解释是:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  ads ALL (null)  (null)  (null)  (null)  826919  Using where

但是我不明白解释的足够多的答案

1 个答案:

答案 0 :(得分:3)

这很慢,因为MySQL优化器正在为该语句生成次优计划。

我们可以使用MySQL EXPLAIN语句查看执行计划的详细信息。


为了回答这个问题,在最坏的情况下,MySQL可以将该子查询视为一个相关的相关子查询,这样它就可以针对外部查询处理的每一行执行。

也就是说,MySQL执行计划可能会为外部查询获取一行,然后检查WHERE子句中的条件。为此,MySQL可能正在执行子查询,将其结果具体化为一个临时表。注意,DISTINCT关键字将导致MySQL执行唯一排序,以消除重复项。一旦结果准备好,MySQL便可以扫描结果以查看是否从外部行找到了该值。根据MySQL优化器的版本,可能没有索引。如果找不到匹配项,则外部查询中的行将被丢弃。

然后MySQL从外部查询中获取下一行,并执行相同的过程。执行子查询,实现结果,然后对其进行扫描以查看name的值。

这可能是大型集合的最坏情况执行计划。

或者,可能只是子查询实现了一次,但是没有索引,因此,对于外部查询的每一行,都必须扫描子查询中的每一行以寻找匹配的name。子查询返回24,000行,这意味着外部查询中被丢弃的每一行可能有24,000个字符串匹配。

另一种可能性是MySQL正在等待获取锁,例如这些表是MyISAM,并且有并发DML操作持有表锁。


我们是否需要更多有关性能降低的可能原因的解释,还是我们应该跳转到一些替代查询模式以获得更好的性能?

要考虑的一些建议:

  • 联接操作或EXISTS子查询,而不是IN子查询
  • 限定所有列引用
  • 不包括多余的括号

通过JOIN操作将查询结果具体化为派生表的演示。在最新版本中对MySQL优化器的改进将允许在派生表上自动创建索引,以提高性能。但是,如果派生表是联接的驱动表,则MySQL可以使用name作为前导列的索引。例如,查询的覆盖索引为... ON marketing.ads (name,tracking_key)

SELECT t.name
     , t.tracking_key 
  FROM ( SELECT d.ad_name
           FROM marketing.fbk_ad_stats_daily d
          WHERE d.date_start >= CURRENT_DATE() + INTERVAL -30 DAY
            AND d.spend > 1
          GROUP
             BY d.ad_name
       ) n
  JOIN marketing.ads t
    ON t.name = n.ad_name

有时EXISTS模式将提供适当的性能,并提供适当的索引。请注意,子查询与外部行相关,子查询中的ad_name的值需要与外部查询中的name的值匹配。

SELECT t.name
     , t.tracking_key 
  FROM marketing.ads t
 WHERE EXISTS ( SELECT 1
                  FROM marketing.fbk_ad_stats_daily d
                 WHERE d.date_start >= CURRENT_DATE() + INTERVAL -30 DAY
                   AND d.spend      > 1
                   AND d.ad_name    = t.name    /*correlated to outer row*/
              )

这种形式的查询将需要检查t中的每一行,因此,子查询将针对被查询的每一行执行(而不以其他方式丢弃)外部查询。

相关问题