MYSQL IN 优化
Posted
技术标签:
【中文标题】MYSQL IN 优化【英文标题】:MYSQL IN Optimisation 【发布时间】:2014-08-21 13:18:17 【问题描述】:我在将我的 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
;
任何关于如何改进这一点的想法都将不胜感激。
【问题讨论】:
使用EXPLAIN
运行它,您将了解它是否达到索引等。
你有没有在实际应用中真正计时过这个语句?真的有问题吗? “如果没坏就不要修”。
【参考方案1】:
IN (SELECT subquery)
的问题在于 mysql “优化”了它,而这往往是一个性能很差的选择。
根据documentation,带有这样一个模板的查询:
WHERE outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)
由优化器自动转换为:
存在的位置(从 ... WHERE subquery_where AND outer_expr=inner_expr 选择 1)
问题是这个子查询是针对外部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
s 上处理的就越少,而且处理得越快。例如,您可以直接从开头过滤 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 ...
来强制执行JOIN
s 的顺序,以防它执行得更好(通常会这样做)。
【讨论】:
【参考方案2】:你是对的,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
;
【讨论】:
您的子选择看起来很狡猾...我很确定它不会产生正确的值。 @AleksG 现在怎么样了? 这会产生错误。正确的语法是SELECT ... FROM ... GROUP BY ... HAVING max(temps.dt) = t.dt LIMIT 1
但是,使用 HAVING
的查询通常很慢,因此我不确定在这种情况下使用 EXISTS
是否会产生任何好处。
@AleksG 让 OP 试试这个。感谢您的帮助!
@DanFromGermany 我的原始查询需要 52.604 秒并返回 6 行,一个用于每个不同的传感器 ID。您的查询需要 50.466 秒并且只返回 1 行,所以它似乎没有按预期工作。【参考方案3】:
事实证明 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 秒查询。
【讨论】:
这不是一回事。如果同一个sensor
中有多个dt
不是0000-00-00 00:00:00
怎么办?您的 GROUP BY sensor
将获取每个传感器的第一个匹配项,无论 dt
是否是该传感器的 MAX(dt)
。【参考方案4】:
尽管您似乎有一个解决方案并且非常愉快,但 Havenard 有一个很好的观点,即不一定每个传感器都正确。我会提出以下建议。
在您的临时表上,在 (sensor, dt) 上有一个索引,然后,您的第一个 from 将是按每个传感器分组的选择,因此每个传感器出现一次,并带有其各自的日期/时间。然后,以此为基础,通过同一个传感器/max(dt) 重新加入 temps 并获取其余数据。
这与 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;
【讨论】:
以上是关于MYSQL IN 优化的主要内容,如果未能解决你的问题,请参考以下文章