【问题标题】:Optimization of relatively basic JOIN and GROUP BY query比较基础的 JOIN 和 GROUP BY 查询的优化
【发布时间】:2017-12-24 08:21:30
【问题描述】:

我有一个相对基本的查询来获取每个对话的最新消息:

SELECT `message`.`conversation_id`, MAX(`message`.`add_time`) AS `max_add_time`
FROM `message` 
LEFT JOIN `conversation` ON `message`.`conversation_id` = `conversation`.`id` 
WHERE ((`conversation`.`receiver_user_id` = 1 AND `conversation`.`status` != -2)
OR (`conversation`.`sender_user_id` = 1 AND `conversation`.`status` != -1))
GROUP BY `conversation_id` 
ORDER BY `max_add_time` DESC
LIMIT 12

message 表包含超过 911000 条记录,conversation 表包含大约 680000 条记录。此查询的执行时间在 4 到 10 秒之间变化,具体取决于服务器上的负载。太长了。

下面是EXPLAIN结果的截图:

原因显然是MAX 和/或GROUP BY,因为下面的类似查询只需要 10 毫秒:

SELECT COUNT(*) 
FROM `message` 
LEFT JOIN `conversation` ON `message`.`conversation_id` = `conversation`.`id` 
WHERE (`message`.`status`=0) 
AND (`message`.`user_id` <> 1) 
AND ((`conversation`.`sender_user_id` = 1 OR `conversation`.`receiver_user_id` = 1))

对应的EXPLAIN结果:

我尝试在没有任何改进的情况下向两个表添加不同的索引,例如:message 上的conv_msg_idx(add_time, conversation_id) 似乎根据第一个EXPLAIN 结果使用,但是查询仍然需要大约 10 秒才能执行.

任何帮助改进索引或查询以缩短执行时间将不胜感激。

编辑:

我已将查询更改为使用INNER JOIN

SELECT `message`.`conversation_id`, MAX(`message`.`add_time`) AS `max_add_time`
FROM `message` 
INNER JOIN `conversation` ON `message`.`conversation_id` = `conversation`.`id` 
WHERE ((`conversation`.`receiver_user_id` = 1 AND `conversation`.`status` != -2)
OR (`conversation`.`sender_user_id` = 1 AND `conversation`.`status` != -1))
GROUP BY `conversation_id` 
ORDER BY `max_add_time` DESC
LIMIT 12

但执行时间仍然是~6秒。

【问题讨论】:

  • 备注:你写了OUTER JOIN,但实际上你在INNER JOIN 上得到了结果(由于conversation 上的WHERE 条件),MySQL 过去常常很难注意到这一点。如果您的结果是您真正想要的,请删除 LEFT
  • INNER JOIN 查询通常要快得多。
  • 我会去规范化并将最新的消息时间戳或 ID(如果消息 ID 是自动递增的)存储在对话表中,并通过触发器保持更新。
  • @dnoeth @EmileEichenberger 感谢您的建议,我已将连接类型更改为INNER JOIN,改进很小
  • @Shadow 我也考虑过这个问题,如果这个查询无法改进,我可能会使用它作为解决方案。感谢您的建议

标签: mysql sql performance indexing query-optimization


【解决方案1】:

您应该在 WHERE 子句中的列和您想要选择的列上创建多列索引(conversation_id 除外)。 (reference) conversation_id 应该是两个表中的索引。

【讨论】:

  • 如果您实际上就这些索引的外观提出了建议,那么这个答案会更加有用。请记住,group by 和 join 也需要索引,mysql 在大多数情况下每个表只能使用单个索引。
【解决方案2】:

尽量避免在 Sql 查询中使用“或”,这会使获取速度变慢。而是使用联合或任何其他方法。

SELECT message.conversation_id, MAX(message.add_time) AS max_add_time FROM message INNER JOIN conversation ON message.conversation_id = conversation.id WHERE (conversation.sender_user_id = 1 AND conversation.status != -1)) GROUP BY conversation_id 联合

SELECT message.conversation_id, MAX(message.add_time) AS max_add_time FROM message INNER JOIN conversation ON message.conversation_id = conversation.id WHERE ((conversation.receiver_user_id = 1 AND conversation.status != -2) ) GROUP BY conversation_id ORDER BY max_add_time DESC LIMIT 12

【讨论】:

  • 感谢您的建议,但即使删除整个 WHERE 子句也不能真正帮助执行时间,在这种情况下,是 MAX()GROUP BY 使查询变慢。
  • 您是否尝试在您发布的同一查询中不使用“或”运行。
  • 是的,我试过不使用 OR,WHERE 子句看起来像这样:... WHERE conversation.sender_user_id = 1 GROUP BY conversation_id ...。 - 几乎没有区别。
【解决方案3】:

不要依赖于单个表 message,而是有两个表:一个用于 message,就像你一样,再加上另一个 thread,它保持消息线程的状态。

是的,添加新消息时需要做更多工作——更新thread 中的一两列。

但它消除了在此查询中引起悲伤的GROUP BYMAX

在进行这种拆分时,看看其他一些列在新表中是否会更好。

【讨论】:

    【解决方案4】:
    SELECT `message`.`conversation_id`, MAX(`message`.`add_time`) AS `max_add_time`
    FROM `message` 
    INNER JOIN `conversation` ON `message`.`conversation_id` = `conversation`.`id` 
    WHERE ((`conversation`.`receiver_user_id` = 1 AND `conversation`.`status` != -2)
    OR (`conversation`.`sender_user_id` = 1 AND `conversation`.`status` != -1))
    GROUP BY `conversation_id` 
    ORDER BY `max_add_time` DESC
    LIMIT 12
    

    如果你的逻辑不影响使用,你可以试试INNER JOIN

    【讨论】:

    • 我尝试过INNER JOIN 而不是LEFT JOIN,但改进几乎不明显
    • 那么显然使用INNER JOIN,即使有轻微影响。
    【解决方案5】:

    您可以通过避免使用 max() 来修改此查询

       select * from(
    select row_number() over(partition by conversation_id  order by add_time desc)p1
                    )t1  where t1.p1=1
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-23
      • 2011-08-29
      • 2012-12-31
      • 1970-01-01
      相关资源
      最近更新 更多