以下查询需要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
但是我不明白解释的足够多的答案
答案 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
中的每一行,因此,子查询将针对被查询的每一行执行(而不以其他方式丢弃)外部查询。