【问题标题】:Is there anyway to optimise this MySQL query?无论如何优化这个 MySQL 查询?
【发布时间】:2012-09-28 14:17:57
【问题描述】:

这需要很长时间才能完成,但希望能够快速提取它收集的信息。

SELECT *
FROM releases
WHERE (artist IN (SELECT artist FROM artist_love WHERE user='Quickinho')
OR
label IN (SELECT label FROM label_love WHERE user='Quickinho')
OR
id IN (SELECT release_id FROM charts_extended WHERE artist IN (SELECT dj FROM dj_love WHERE user='Quickinho'))
OR
id IN (SELECT artist FROM releases WHERE id IN (SELECT release_id FROM charts_extended WHERE user='Quickinho'))
OR
id IN (SELECT label FROM releases WHERE id IN (SELECT release_id FROM charts_extended WHERE user='Quickinho')))
AND
id NOT IN (SELECT release_id FROM charts_extended WHERE user='Quickinho')
ORDER BY date DESC
LIMIT 0,102

【问题讨论】:

  • 请将表 CREATE 添加到问题中。并添加此查询返回的内容EXPLAIN ...(...是您的查询)
  • 子查询比 JOIN 相对慢,并且旧 MySQL 服务器版本不支持。考虑使用 JOIN。
  • 您可能在那里缺少一些索引。你能发一个EXPLAIN吗?

标签: mysql join subquery


【解决方案1】:
...from releases
WHERE (artist IN (SELECT artist FROM artist_love WHERE user='Quickinho')

我建议你使用JOIN 而不是IN (SELECT..)

你可以做类似的事情

select r.* from releases r, artist_love al 
where r.artist = al.artist and al.user='Quickinho'

【讨论】:

    【解决方案2】:

    IN() 和 NOT IN() 子查询优化不佳
    MySQL 将子查询作为外部查询中每一行的依赖子查询执行。这是 MySQL 5.5 和更早版本中严重性能问题的常见原因。查询可能应该分别重写为 JOIN 或 LEFT OUTER JOIN。

    选择 *

    选择所有带有 * 通配符的列将导致如果表的架构发生变化,查询的含义和行为也会发生变化,并可能导致查询检索到过多的数据。

    【讨论】:

      【解决方案3】:

      首先 - 将 JOIN 关系中使用的所有字段都编入索引。

      然后试试这个查询 -

      SELECT
        r.*
      FROM
        releases r
      LEFT JOIN (SELECT artist FROM artist_love WHERE user='Quickinho') al
        ON al.artist = r.artist
      LEFT JOIN (SELECT label FROM label_love WHERE user='Quickinho') ll
        ON ll.label = r.label
      LEFT JOIN (
          SELECT release_id FROM charts_extended ce
          INNER JOIN (SELECT dj FROM dj_love WHERE user='Quickinho') djl
            ON djl.dj = ce.artist
          ) ce
        ON r.id = ce.release_id
      LEFT JOIN (
          SELECT artist FROM releases r
          INNER JOIN (SELECT release_id FROM charts_extended WHERE user='Quickinho') ce
            ON r.id = release_id
        ) r2
        ON r2.artist = r.id OR r2.label = r.id
      
      LEFT JOIN (SELECT release_id FROM charts_extended WHERE user='Quickinho') ce2
        ON ce2.release_id = r.id
      
      WHERE
        (al.artist IS NOT NULL OR ll.label IS NOT NULL OR ce.release_id IS NOT NULL OR r2.id IS NOT NULL)
        AND ce2.release_id IS NULL
      GROUP BY
        r.id
      

      【讨论】:

      • "JOIN (SELECT ...)" - 我一点也不喜欢。我建议将 WHERE 条件移动到“IS NOT NULL”检查所在的 WHERE(所以它可以是“JOIN table ON ...”),或者将它作为连接的“ON”部分的一部分(所以“ON ... AND user='...')。
      • 我也不喜欢子查询。因此,过滤器可以移动到 WHERE 子句。关于“ON...AND 用户”,您不应该为 LEFT JOIN 这样做。
      【解决方案4】:

      避免任何子选择(虽然没有测试,所以请原谅任何错别字)

      SELECT *
      FROM releases
      LEFT OUTER JOIN artist_love ON releases.artist = artist_love.artist AND artist_love.user = 'Quickinho'
      LEFT OUTER JOIN label_love ON releases.label = label_love.label AND label_love.user = 'Quickinho'
      LEFT OUTER JOIN charts_extended ON releases.id = charts_extended.release_id
      LEFT OUTER JOIN dj_love ON charts_extended.artist = dj_love.dj AND dj_love.user = 'Quickinho'
      LEFT OUTER JOIN releases releases1 ON releases.id = releases1.artist
      LEFT OUTER JOIN charts_extended charts_extended1 ON charts_extended1.artist = releases1.id AND charts_extended1.user = 'Quickinho'
      LEFT OUTER JOIN releases releases2 ON releases.id = releases2.label
      LEFT OUTER JOIN charts_extended charts_extended2 ON charts_extended2.artist = releases2.id AND charts_extended2.user = 'Quickinho'
      LEFT OUTER JOIN charts_extended charts_extended3 ON charts_extended3.release_id = releases.id AND charts_extended3.user = 'Quickinho'
      WHERE (artist_love.user IS NOT NULL
      OR label_love.user IS NOT NULL
      OR dj_love.user IS NOT NULL
      OR charts_extended1.user IS NOT NULL
      OR charts_extended2.user IS NOT NULL)
      AND charts_extended3.user IS NULL
      

      【讨论】:

        【解决方案5】:

        其他人提供的优化查询可能还不够快。

        假设您的原始查询需要 120 秒才能执行,而最佳优化查询仍然需要 30 秒,但您需要 5 秒或更短的响应时间。你能做什么?

        预填充!

        运行由定期执行的cron 作业 触发的查询,例如每小时。像这样使用INSERT SELECT 查询:

        INSERT INTO releases_queried
        SELECT -- your query (your original one or one of the optimized ones)
        

        MySQL Manual INSERT-SELECT。然后你会得到结果来自

        SELECT * FROM releases_queried
        

        立即在 毫秒 内。这是一种众所周知的改进响应时间的技术。如果查询所需的数据始终可用,则效果很好。

        现实世界的用法

        StackOverflow 本身有许多复杂的查询不是按请求完成的,而是异步完成的。徽章不是在每次访问时计算的,而是按 cron 计算的。

        【讨论】:

        • 感谢 Nalply,我正在运行的查询实际上只需要大约 3 秒 - 也许它已尽可能优化?上面提供的其他解决方案似乎需要更长的时间,并且经常导致发布表锁定。
        • 3 秒对你来说太长了?如果可以的话,你需要像我的提议一样预先创建结果。
        【解决方案6】:

        Kickstart 的解决方案是正确的想法(尽管我建议您尽可能在 USER 上加入,但让“user = 'Quickinho'”出现这么多次并不是一个好习惯),然后考虑为部分或全部添加索引以下字段:

        • artist_love.artist
        • label_love.label
        • charts_extended.release_id
        • dj_love.dj
        • releases.artist
        • releases.label
        • charts_extended.release_id

        虽然我不能说我能想到你想用这个做什么。可能有更好的解决方案。

        【讨论】:

          【解决方案7】:

          你可以搜索key_cache,SQLPartition,performance tuning;

          【讨论】:

            【解决方案8】:

            您可以使用JOIN 来提高性能。在 JOIN 中,RDBMS 可以创建一个更适合您的查询的执行计划,这与子查询不同,子查询将运行所有查询并加载所有数据以进行处理。

            【讨论】:

              猜你喜欢
              • 2010-11-15
              • 1970-01-01
              • 2012-04-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多