以下查询需要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优化器正在为语句生成一个次优的计划。
我们可以使用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
中的每一行,因此将通过外部查询为每个检查(而不是以其他方式丢弃)的行执行子查询。