我无法将我的SQL查询的IN语句调整为EXISTS。我知道IN速度较慢,这似乎反映在查询的性能中。
SELECT
t.dt as 'Log Time',
sn.name as 'Snake Name',
sen.type as 'Sensor Type',
t.temp as Temperature
FROM
temps as t
JOIN
sensors as sen ON t.sensor = sen.sensorid
JOIN
locations as l ON sen.location = l.id
JOIN
snakes as sn ON sen.location = sn.location
WHERE
dt IN (SELECT
max(dt)
FROM
temps
GROUP BY sensor)
ORDER BY sn.name ASC , sen.type DESC
;
如何改进这一点的任何想法都将受到赞赏。
答案 0 :(得分:1)
IN (SELECT subquery)
的问题在于MySQL使用通常非常糟糕的性能选择“优化”它。
根据documentation,使用如下模板的查询:
WHERE outer_expr IN(SELECT inner_expr FROM ... WHERE subquery_where )
由优化器自动转换为:
WHERE EXISTS(SELECT 1 FROM ... WHERE subquery_where AND outer_expr = inner_expr )
问题是该子查询是针对外部SELECT
中的每一行执行的。如果我们在WHERE
之前讨论数百,数千甚至数十万行,MySQL将花费很长时间来消化这个东西,一遍又一遍地迭代相同的表来查找每个匹配。不酷!
解决方案是强制它使用临时表创建JOIN
。这个想法是,尽管创建临时表的速度比简单查询慢,并且需要更多内存,但它确实比数千个查询更快。所以这就是你要做的事情:
SELECT
t.dt `Log Time`,
sn.name `Snake Name`,
sen.type `Sensor Type`,
t.temp `Temperature`
FROM
temps t
JOIN
sensors sen ON t.sensor = sen.sensorid
JOIN
locations l ON sen.location = l.id
JOIN
snakes sn ON sen.location = sn.location
JOIN
(SELECT sensor, MAX(dt) maxdt FROM temps GROUP BY sensor) m
ON m.maxdt = t.dt AND m.sensor = t.sensor
ORDER BY sn.name ASC, sen.type DESC;
当JOIN
使用子查询SELECT
时,它不会做出愚蠢的假设。在场景后面创建一个临时表,它将按照您的说法执行JOIN
。
然而,请注意这可以进一步优化。我们可以通过在JOIN
表中保留游戏中的少量记录来实现此目的。您越早撤出查询中不需要的记录,MySQL将不得不在随后的JOIN
上处理的内容越少,它就越快。例如,您可以直接从头开始过滤所需的行,只需重新组织temps
序列:
JOIN
这个看似简单的变化对于性能非常有意义,这个查询应该比第一个快得多,特别是如果SELECT
t.dt `Log Time`,
sn.name `Snake Name`,
sen.type `Sensor Type`,
t.temp `Temperature`
FROM
(SELECT sensor, MAX(dt) maxdt FROM temps GROUP BY sensor) m
JOIN
temps t ON m.maxdt = t.dt AND m.sensor = t.sensor
JOIN
sensors sen ON t.sensor = sen.sensorid
JOIN
locations l ON sen.location = l.id
JOIN
snakes sn ON sen.location = sn.location
ORDER BY sn.name ASC, sen.type DESC;
是一个大表。
您还可以使用temps
强制执行SELECT STRAIGHT_JOIN ...
的顺序,以防它的表现更好(通常情况下)。
答案 1 :(得分:0)
您是对的,IN
子查询的通常比EXISTS
慢。
EXISTS
的工作方式不同,因为您可以直接在其中使用以前的列。您还可以使用LIMIT
来限制您的子集仅限于您真正需要的金额。使用EXISTS
时,选择的内容无关紧要,因为它只是询问:是否至少返回了1行。
确保在每列上使用前缀。
SELECT
t.dt as 'Log Time',
sn.name as 'Snake Name',
sen.type as 'Sensor Type',
t.temp as Temperature
FROM
temps as t
JOIN
sensors as sen ON t.sensor = sen.sensorid
JOIN
locations as l ON sen.location = l.id
JOIN
snakes as sn ON sen.location = sn.location
WHERE
EXISTS(
SELECT 'hi'
FROM temps
GROUP BY temps.sensor
HAVING max(temps.dt) = t.dt
LIMIT 1
)
ORDER BY sn.name ASC , sen.type DESC
;
答案 2 :(得分:0)
事实证明,EXISTS和IN都不是最佳解决方案。玩完游戏后,我想出了以下内容:
SELECT distinct
t.dt as 'Log Time',
sn.name as 'Snake Name',
sen.type as 'Sensor Type',
t.temp as Temperature
FROM
(SELECT
*
FROM
temps
ORDER BY dt DESC) as t
JOIN
sensors as sen ON t.sensor = sen.sensorid
JOIN
locations as l ON sen.location = l.id
JOIN
snakes as sn ON sen.location = sn.location
WHERE
dt != '0000-00-00 00:00:00'
GROUP BY sensor
ORDER BY sn.name ASC , sen.type DESC
运行时间为0.047秒,与原始的~50秒查询相反。
答案 3 :(得分:0)
虽然看起来你有一个解决方案和非常好的时间,但Havenard有一个很好的观点,关于每个传感器不一定正确。我建议如下。
在你的临时表上,索引(传感器,dt),然后,你的第一个将是由每个传感器分组的选择,因此每个传感器出现一次及其各自的日期/时间。然后,使用它作为基础,通过相同的传感器/最大值(dt)重新加入临时值并获取其余数据。
这与Havenard发布的内容非常接近,除了我正在预先移动我的预查询并添加" STRAIGHT_JOIN"按照书面顺序强制加入。从最大日期/时间开始,使用非常有限的设置,然后加入其余部分以获取描述和临时信息。
SELECT STRAIGHT_JOIN
t.dt `Log Time`,
sn.name `Snake Name`,
sen.type `Sensor Type`,
t.temp `Temperature`
FROM
( select t1.sensor, max( t1.dt ) as MaxDT
from temps t1
group by t1.sensor ) PreQuery
JOIN temps t
on PreQuery.sensor = t.sensor
AND PreQuery.MaxDT = t.dt
JOIN sensors sen
ON PreQuery.sensor = sen.sensorid
JOIN locations l
ON sen.location = l.id
JOIN snakes sn
ON sen.location = sn.location
ORDER BY
sn.name,
sen.type DESC;