【问题标题】:speed up mysql query with multiple subqueries使用多个子查询加速 mysql 查询
【发布时间】:2011-04-12 11:39:30
【问题描述】:

我想知道是否有一种方法可以加快由多个子查询排序的 mysql 查询。

在与音乐相关的网站上,用户可以喜欢不同的东西,例如艺术家、歌曲、专辑等。这些“喜欢”都存储在同一个表中。现在我想显示按用户朋友和所有用户的“喜欢”数量排序的艺术家列表。我想向所有艺术家展示,包括那些根本没有点赞的人。

我有以下疑问:

SELECT `artists`.*, 

    // friend likes
    (SELECT COUNT(*)
     FROM `likes`
     WHERE like_type = 'artist'
     AND like_id = artists.id
     AND user_id IN (1,2,3,4, etc) // ids of friends
     GROUP BY like_id
    ) AS `friend_likes`, 

    // all likes
    (SELECT COUNT(*)
     FROM `likes`
     WHERE like_type = 'artist'
     AND like_id = artists.id
     GROUP BY like_id
    ) AS `all_likes`

FROM artists
ORDER BY 
    friend_likes DESC, 
    all_likes DESC, 
    artists.name ASC

在有 2000 行的艺术家表上查询需要 ± 1.5 秒。恐怕随着桌子越来越大,这需要越来越长的时间。我尝试使用 JOINS 似乎无法正常工作,因为子查询包含 WHERE 语句。

任何正确方向的想法将不胜感激!

【问题讨论】:

  • 在其他人之前 - 将 EXPLAIN 放在 SELECT 语句之前会得到什么?
  • 您是否已将艺术家表中的连接列编入索引?

标签: mysql optimization subquery


【解决方案1】:

尝试使用JOINs 代替子查询:

SELECT
  artists.*, -- do you really need all this?
  count(user_id) AS all_likes,
  sum(user_id IN (1, 2, 3, 4)) AS friend_likes
FROM artists a
LEFT JOIN likes l
  ON l.like_type = 'artist' AND l.like_id = a.id
GROUP BY a.id
ORDER BY 
  friend_likes DESC, 
  all_likes DESC, 
  artists.name ASC;

如果这不能使查询更快,请尝试添加索引,或考虑选择更少的字段。

【讨论】:

  • 恐怕这会对性能有所帮助。内部查询优化器可能已经在他的环境条件下尽可能地减少了查询。
  • @d-live:我可以想象优化器已经用连接替换了每个子查询,但它是否也会将两个子查询放在一起?
  • 啊哈是的。我评论了 JOIN 语法与非 JOIN 语法。是的,您将子查询组合在一起 +1 是对的:)
  • @d-live:我在字段 likes.like_id 上添加了一个索引,现在查询运行得更快了,±0.03 秒。当字段可以引用不同表中的不同列时,是否可以向字段添加索引? (album_id、song_id 或 Artist_id)。 @Martijn:感谢您向我展示如何使用 JOIN 来做到这一点!查询现在看起来更整洁了,但奇怪的是运行速度变慢了。 ± 1.7 秒没有索引字段,± 0.06 有索引字段,相对于 ± 1.5 和 ± 0.03
  • @smek:只是另一个脑电波;你把like_type 设为枚举了吗?
【解决方案2】:

您需要稍微分解一下,看看时间都花在了哪里。您绝对正确,2000 行的 1.5 秒不能很好地扩展。我怀疑您需要查看索引和外键关系。分别查看每个 count/group-by 查询,以便尽可能地调整它们,然后重新组合。

【讨论】:

    【解决方案3】:

    尝试使用内联 IF() 滚动到查询中并遍历表/加入 ONCE

    SELECT STRAIGHT_JOIN
      artists.*
      , LikeCounts.AllCount
      , LikeCounts.FriendLikeCount
    FROM  
      (SELECT
        like_id
        , count(*) AllCount
        , sum( If( User_id in ( 1, 2, 3, 4 ), 1, 0 ) as FriendLikeCount
      FROM
        friend_likes
      WHERE
        like_type = 'artist'
      GROUP BY 
        like_id ) LikeCounts
    JOIN artists ON LikeCounts.like_id = artists.id
    ORDER BY
      LikeCounts.FriendLikeCount DESC
      , LikeCounts.AllCount DESC
      , artists.name ASC
    

    【讨论】:

    • IN(1,2,3,4) 可以重写为 BETWEEN 1 AND 4,将 4 个测试减少为 2 个。
    • @Johan,是的,但发布问题的人实际上提供了一个可能是 1、8、12、38、53、2300 的列表...我只是作为简单示例发布的。跨度>
    • 原始问题说明了 1,2,3 等。所以这之间是有道理的。如果列表真的是随机的,那么 user_id 上的内部连接可能会更有序。
    • @Johan,我同意可能的内部加入...但是,除非有实质性目的,否则不应乱编辑他人的帖子内容。不应该仅仅因为你有不同的 SQL 样式的语句就“仅仅因为”。最初输入的方式显示第一个查询与下一个查询的关系更容易......不要管其他人的样式,除非它完全没有被没有获得 { } 用于代码格式化的用户格式化。
    • 带有 user_ids 的列表是随机的。我只是以 1,2,3 为例。 @Johan,您的查询只返回有一个或多个喜欢的艺术家,而不是所有艺术家。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 2012-12-08
    • 1970-01-01
    • 2016-10-09
    • 1970-01-01
    相关资源
    最近更新 更多