【问题标题】:MYSQL IN OptimisationMYSQL IN 优化
【发布时间】: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 运行它,您将了解它是否达到索引等。
  • 你有没有在实际应用中真正计时过这个语句?真的有问题吗? “如果没坏就不要修”。

标签: mysql sql optimization exists


【解决方案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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多