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 在随后的JOINs 上处理的就越少,而且处理得越快。例如,您可以直接从开头过滤 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 ... 来强制执行JOINs 的顺序,以防它执行得更好(通常会这样做)。

【讨论】:

【参考方案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 优化的主要内容,如果未能解决你的问题,请参考以下文章

优化 MySQL NOT IN( 查询

MYSQL性能调优06_分页查询优化JOIN关联查询优化in和exsits优化count(*)查询优化

MySQL 查询优化:IN() 与 OR

如何优化 MySQL 中的 IN 子查询?

MySQL in查询优化

MySQL 子查询优化 - where not in(子查询)