为什么WHERE语句中的子查询在MYSQL中如此之慢?

问题描述 投票:2回答:1

以下查询需要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

但我不明白解释足以辨别答案

mysql
1个回答
3
投票

它很慢,因为MySQL优化器正在为语句生成一个次优的计划。

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


为了回答这个问题,最糟糕的情况是,MySQL可以将该子查询视为依赖相关子查询,以便为外部查询处理的每一行执行该子查询。

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

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

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

或者,它可能只是子查询已实现一次,但没有索引,因此子查询中的每一行都必须针对外部查询的每一行扫描匹配的name。子查询返回24,000行,外部查询中每行丢弃可能有24,000个字符串匹配。

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


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

一些建议要考虑:

  • 连接操作或EXISTS子查询而不是IN子查询
  • 限定所有列引用
  • 不包括外来的parens

使用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中的每一行,因此将通过外部查询为每个检查(而不是以其他方式丢弃)的行执行子查询。

© www.soinside.com 2019 - 2024. All rights reserved.