【发布时间】: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