【问题标题】:Improving this MySQL Query - Select as sub-query改进这个 MySQL 查询 - 选择作为子查询
【发布时间】:2013-08-08 14:39:51
【问题描述】:

我有这个问题

  SELECT  
   shot.hole AS hole,
   shot.id AS id,
   (SELECT s.id FROM shot AS s 
      WHERE s.hole = shot.hole AND s.shot_number > shot.shot_number AND shot.round_id = s.round_id 
       ORDER BY s.shot_number ASC LIMIT 1) AS next_shot_id,
   shot.distance AS distance_remaining,
   shot.type AS hit_type,
   shot.area AS onto
  FROM shot 
  JOIN course ON shot.course_id = course.id
  JOIN round ON shot.round_id = round.id
  WHERE round.uID = 78

这会在大约 0.7 秒内返回 900~ 行。这没问题,但需要更多这样的行

(SELECT s.id FROM shot AS s 
 WHERE s.hole = shot.hole AND s.shot_number > shot.shot_number AND shot.round_id = s.round_id 
 ORDER BY s.shot_number ASC LIMIT 1) AS next_shot_id,

例如

   (SELECT s.id FROM shot AS s 
    WHERE s.hole = shot.hole AND s.shot_number < shot.shot_number AND shot.round_id = s.round_id 
    ORDER BY s.shot_number ASC LIMIT 1) AS past_shot_id,

添加这会将加载时间增加到 10 秒,这太长了,页面通常根本无法加载,或者 MySQL 只是锁定,使用 show processlist 表明查询只是坐在那里 sending data

删除这些子查询中的ORDER BY s.shot_number ASC 子句将查询时间减少到0.05 秒,这要好得多。但是需要ORDER BY 来确保返回下一个或过去的行(镜头),而不是任何旧的随机行。

如何改进此查询以使其运行更快并返回相同的结果。也许my approach 用于获取下一行和过去的行是次优的,我需要寻找一种不同的方式来返回下一行和上一行的 ID?

编辑 - 附加背景信息

查询在我的测试域(子域)上很好。但是当移动到实时域时,问题就开始了。几乎没有任何改变,但由于这些新的缓慢查询,整个站点都停止了。重点说明:

  • 不同的域
  • /var/www 中的不同文件夹
  • 相同的数据库
  • 相同的数据库凭据
  • 相同的代码
  • 添加索引以尝试修复 - 这没有帮助

这些是否会影响加载时间?

【问题讨论】:

  • 你能弄清楚如何将相关子查询重写为不相关子查询吗?或者完全省略子查询>
  • @JakeNoble 。 . .镜头是增量分配的吗?有差距吗?
  • 这应该是可能的@Strawberry - 应该提高速度吗?
  • @GordonLinoff 有差距,出手可能是 1,2,4,6,8
  • :-) @jakenoble 如果我不认为它会,我不会建议它。也就是说,我之前错了……

标签: mysql subquery query-optimization


【解决方案1】:

这将在一分钟内被标记为“不是答案”,但它说明了一种可能的解决方案,而无需简单地将其放在盘子上交给您......

 SELECT * FROM ints;
 +---+
 | i |
 +---+
 | 0 |
 | 1 |
 | 2 |
 | 3 |
 | 4 |
 | 5 |
 | 6 |
 | 7 |
 | 8 |
 | 9 |
 +---+

 SELECT x.i, MIN(y.i) FROM ints x LEFT JOIN ints y ON y.i > x.i GROUP BY x.i;
 +---+----------+
 | i | MIN(y.i) |
 +---+----------+
 | 0 |        1 |
 | 1 |        2 |
 | 2 |        3 |
 | 3 |        4 |
 | 4 |        5 |
 | 5 |        6 |
 | 6 |        7 |
 | 7 |        8 |
 | 8 |        9 |
 | 9 |     NULL |
 +---+----------+

【讨论】:

  • 我知道你用这种方法去哪里了。使用 JOIN 而不是另一个子查询选择。如果 ints 缺少数字,这会起作用吗?
  • 是的。它只考虑一个数字是否大于另一个数字 - 而不是大多少!
  • 我也很喜欢这个选项,如果你再次左键加入 MAX() 以获得先前的镜头,他们将拥有所有 3 个关键 ID(之前、当前、下一个)并且可以加入用于在任一方向上进行比较的详细信息的拍摄表...
  • @Strawberry 我使用了 DRapp 的解决方案,该解决方案很大程度上基于您的想法 - 感谢您的输入 +1
【解决方案2】:

为了扩展草莓的答案,对“预查询”进行额外的左连接以获取所有先前/下一个 ID,然后加入以获取您需要的任何详细信息。

select
      Shot.ID,
      Shot.Hole,
      Shot.Distance as Distance_Remaining,
      Shot.Type as Hit_Type,
      Shot.Area as Onto
      PriorShot.Hole as PriorHole,
      PriorShot.Distance as PriorDistanceRemain,
      NextShot.Hole as NextHole,
      NextShot.Distance as NextDistanceRemain
   from
      ( SELECT 
              shot.id, 
              MIN(nextshot.id) as NextShotID,
              MAX(priorshot.id) as PriorShotID
           FROM 
              round 
                 JOIN shot 
                    on round.id = shot.round_id
                    LEFT JOIN shot nextshot
                       ON shot.round_id = nextshot.round_id
                       AND shot.hole = nextshot.hole
                       AND shot.shot_number < nextshot.shot_number
                    LEFT JOIN shot priorshot
                       ON shot.round_id = priorshot.round_id
                       AND shot.hole = priorshot.hole
                       AND shot.shot_number > priorshot.shot_number
           WHERE
              round.uID = 78
           GROUP BY 
              shot.id ) AllShots
         JOIN Shot
            on AllShots.id = Shot.ID
            LEFT JOIN shot PriorShot
               on AllShots.PriorShotID = PriorShot.ID
            LEFT JOIN shot NextShot
               on AllShots.NextShotID = NextShot.ID

内部查询仅获取round.uID = 78的那些,然后您可以根据需要加入下一个/前一个。我没有将连接添加到课程和圆桌会议,因为没有显示结果列,但可以轻松添加。

【讨论】:

  • 这用我的数据在 0.2 秒内执行,我更快,但不是非常快。这些查询的结果被缓存了,但是页面上有几个,所以初始加载时间可能很重要,每个加载时间为 0.2 秒 - 仍然比我的解决方案更好
  • 我现在已经实现了这个,有点任务,因为有很多东西要重写。但它现在工作得更快了,感谢@DRapp
  • @jakenoble,如果您有一堆您感兴趣的“uID”用户,您至少需要将 WHERE 子句更改为“IN (78, blah, blah)”,并且该字段list 和 group by 以包括您想要展示的任何其他“用户”。此外,LEFT-JOIN 包括镜头的相应用户。
【解决方案3】:

我想知道下面的表现如何。它将连接操作替换为字符串操作。

  SELECT shot.hole AS hole, shot.id AS id,
         substring_index(substring_index(shots, ',', find_in_set(shot.id, ss.shots) + 1), ',', -1
                        ) as nextsi,
         substring_index(substring_index(shots, ',', find_in_set(shot.id, ss.shots) - 1), ',', -1
                        ) as prevsi,
         shot.distance AS distance_remaining, shot.type AS hit_type, shot.area AS onto
  FROM shot JOIN
       course
       ON shot.course_id = course.id JOIN
       round
       ON shot.round_id = round.id join
       (select s.round_id, s.hole, group_concat(s.id order by s.shot_number) as shots
        from shot s
        group by s.round_id, s.hole
       ) ss
       on ss.round_id = shot.round_id and ss.hole = shot.hole
  WHERE round.uID = 78

请注意,这并不完全有效 - 它会在第一次和最后一次拍摄时产生错误的结果。在修复这些细节之前,我想知道性能如何。

【讨论】:

  • 我不熟悉 MySQL 的 substring_index 但这些代码给了我Error in query (1582): Incorrect parameter count in the call to native function 'substring_index'
  • @jakenoble 。 . .那是因为我把第二个参数留给了函数。
  • 我不认为你在定义sError in query (1054): Unknown column 's.round_id' in 'field list'
猜你喜欢
  • 1970-01-01
  • 2013-07-21
  • 1970-01-01
  • 2011-10-27
  • 1970-01-01
  • 1970-01-01
  • 2019-10-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多