【问题标题】:Optimizing/Alternative to slow MySQL query with multiple calculations, joins and order bys使用多个计算、连接和排序来优化/替代缓慢的 MySQL 查询
【发布时间】:2021-11-25 00:12:21
【问题描述】:

我有一个复杂的 MySQL 查询,其中包含多个计算和连接,以按位置检索承包商列表。承包商表(用户)包含 100,000 多行并且还在增长。我遇到的问题是查询需要超过 1.5 秒才能执行,这会导致页面加载显着延迟。

我发现去掉 ORDER BY 子句后,速度明显提高(

还值得注意的是,我已按照其他帖子中的建议添加了索引,但我相信您在对计算列进行排序时无法进一步优化。 (如有错误请指正)

这是查询(为简单起见,我删除了几个列和连接,但查询仍然需要相同的时间来执行):

SELECT `users`.`id`,
       `users`.`username`,
       IF (Max(up.premium_expires_at) > Now(), 1, 0) AS `is_premium`,
       IF (users.last_online_at >= Now() - INTERVAL 30 day, 1, 0) AS `recent_login`,
       IF (da.id IS NOT NULL, 1, 0) AS `is_available`,
       ( 3959 * Acos(Cos(Radians(53.80592)) * Cos(Radians(lat)) * Cos(
                              Radians(lng) - Radians(-1.53834
                                                  )) + Sin(Radians(53.80592)) *
                                                       Sin(Radians(lat))) ) AS
        `distance`
FROM   `users`
       INNER JOIN `users_places` AS `up`
               ON `users`.`id` = `up`.`user_id`
       INNER JOIN `places` AS `mp`
               ON `users`.`place_id` = `mp`.`id`
       LEFT JOIN `users_dates_available` AS `da`
              ON `da`.`user_id` = `users`.`id`
                 AND `from` <= Curdate()
                 AND `to` >= Curdate()
       LEFT JOIN (SELECT user_id,
                         Sum(score) AS score
                  FROM   users_feedback
                  WHERE  status = 1
                  GROUP  BY user_id) AS feedback
              ON `users`.`id` = `feedback`.`user_id`
WHERE  `users`.`status` = 1
       AND `users`.`approved` = 1
GROUP  BY `users`.`id`
HAVING `distance` < 50
ORDER  BY `is_premium` DESC,
          `recent_login` DESC
LIMIT  5 

这是 EXPLAIN 的结果

所以我想我的问题是:在网页上显示这些数据的最快方法是什么?

我的尝试:

  1. 查询是 Laravel 应用程序的一部分。我尝试在没有 ORDER BY 的情况下运行查询并按 PHP 排序。但是执行时间仍然很慢。

  2. 在没有左连接的情况下运行查询,我注意到速度有了显着提高。但是,查询必须使用 LEFT 连接来进行 SELECT 条件中的计算(我们正在检查 NULL 值)。

  3. 使用视图 - 使用预编译视图的查询速度仍然相同。

我能想到的唯一其他选择是创建一个包含所有计算字段的临时表并对其进行查询。但是,这不会存储“距离”列,因为这是特定于运行查询的用户,我仍将按计算列排序。

是否有其他选项或其他方法来优化我缺少的此查询?谢谢

【问题讨论】:

  • 也许你可以把它拆分一下,用 PHP 做一些逻辑。就像从 SELECT users.id, users.username WHERE users.status = 1 AND users.approved = 1 开始然后做一些其他简单的 SELECT 语句和 PHP 排序用正确的信息来显示你的数组
  • @Bolli - 但前提是只有极少数行的状态=1 且已批准=1。

标签: mysql sql


【解决方案1】:

查询似乎没有使用feedback,所以删除LEFT JOIN。这将节省一些浪费的精力。

同样,places 似乎没什么用,除了作为存在测试。

latlng 在哪个表中? (如果不知道 每个 列在哪个表中,我无法完成分析。)

fromto 的数据类型是否为 DATE?如果是这样,涉及他们的WHERE 子句似乎是“今天任何时候”。对吗?

把其中的一些清理干净。之后,我可能会建议将其中一个联接移动到GROUP BYLIMIT 之后。或者,GROUP BY 也可以去掉。

一些可能有用的索引:

users:  INDEX(status, approved, id,  username, last_online_at, place_id)
up:  INDEX(user_id,  premium_expires_at)
da:  INDEX(user_id,  id)
users_feedback:  INDEX(status, user_id)

距离

距离查询的问题在于简单的SELECT 需要检查表中的每一行。这很慢。我有一篇关于如何提高一般“查找最近”问题的性能的博客。它讨论了 5 种方法,从效率最低的(你的代码做什么)开始:http://mysql.rjweb.org/doc.php/find_nearest_in_mysql

【讨论】:

  • 感谢您的建议。抱歉,feedback 表在SELECT 中使用,但我删除以简化示例。 latlng 列在 places 中,因此也需要此表。这是正确的关于从和到日期。我已经按照建议添加了索引(谢谢),但速度仍然高于 ~1.3 秒。作为目前的解决方法,我选择 50 英里半径内的所有用户并在 PHP 中进行过滤/排序。它将页面加载时间减半 - 但它仍然不是“快速”,而且这种方法与 Laravel 的分页不兼容。
  • @kinggs - 注意:“简化”有助于集中 Q+A,但可能会导致更大查询的“错误”答案。
  • @kinggs - “查找最近的”是一个具有挑战性的问题。您的“在 50 内查找所有内容”是等效的。请参阅我添加的段落。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 2014-09-08
  • 2020-11-05
  • 2011-12-05
  • 1970-01-01
  • 2020-01-02
相关资源
最近更新 更多