方法一——改进原始查询
我很确定在这种情况下 INNER JOIN 就足够了,没有理由做一个RIGHT JOIN(如果id 存在于comm 它也将存在于comments)。 INNER JOINs 可以导致better performance。
此外,你真的想把LIMIT 10 推到comm 里面(顺便说一下,把它和ORDER BY 放在一起):
- 一方面,不将
LIMIT 10和ORDER BY放在一起不会为您提供十个最近发布的主题(comm的排序子查询不一定会保留在您LIMITing 的最终结果中。)
- 此外,在最里面的聚合子查询中强制执行
LIMIT 将鼓励基于成本的优化器偏爱nested loops(准确地说是10)而不是hash 或merge joins(10 个嵌套循环是迄今为止最快的任何可观大小的comments表。)
所以,你的查询应该重写为:
SELECT `comments`.* FROM `comments`
INNER JOIN (
SELECT MAX( id ) AS id, core_id, topic_id
FROM comments
GROUP BY core_id, topic_id
ORDER BY id DESC
LIMIT 10
) comm
ON comm.id = comments.id
ORDER BY comments.id
最后,使用EXPLAIN 来查看查询在做什么。不要忘记检查您是否已在 comments.id 上创建索引以帮助处理 JOIN 嵌套循环。
方法 2 - 不同的方法
请注意,虽然上述查询仍可能比您的原始查询更快,最里面的comm 子查询可能仍然是一个重要的瓶颈,如果它导致@ 的全表扫描987654346@。这实际上取决于数据库在同时看到GROUP BY、ORDER BY 和LIMIT 时的智能程度。
如果EXPLAIN表示子查询正在做表扫描,那么你可以尝试结合使用SQL和应用级逻辑以获得最佳性能假设我已经理解您的要求正确,并且您想确定十个不同主题中最近发布的十个 cmets:
# pseudo-code
core_topics_map = { }
command = "SELECT * FROM comments ORDER BY id DESC;"
command.execute
# iterate over the result set, betting that we will be able to break
# early, bringing only a handful of rows over from the database server
while command.fetch_next_row do
# have we identified our 10 most recent topics?
if core_topics_map.size >= 10 then
command.close
break
else
core_topic_key = pair(command.field('core_id'), command.field('topic_id'))
if not defined?(core_topics_map[core_topic_key]) then
core_topics_map[core_topic_key] = command.field('id')
end
end
done
# sort our 10 topics in reverse chronological order
sort_by_values core_topics_map
在大多数情况下(即,假设您的应用程序的数据库驱动程序在将控制权从execute 交还给您之前不会尝试为您将所有行缓冲到内存中),上述内容只会获取少数行,始终使用一个索引,不涉及表扫描。
方法 3 - 混合方法
如果十秒前我知道最近的十个 cmets 是什么,我以后再问这个问题时能聪明点吗? 除非可以从数据库中删除cmets,那么答案是肯定的,因为我知道,当我再次提问时,所有评论ID都会大大大于或等于我得到的最早的评论ID在我的最后一个查询中。
因此,我可以使用附加条件 WHERE id >= :last_lowest_id:
将最里面的查询重写为
更具选择性
SELECT `comments`.* FROM `comments`
INNER JOIN (
SELECT MAX( id ) AS id, core_id, topic_id
FROM comments
WHERE id >= :last_lowest_id
GROUP BY core_id, topic_id
ORDER BY id DESC
LIMIT 10
) comm
ON comm.id = comments.id
ORDER BY comments.id
当您第一次运行查询时,使用0 代替:last_lowest_id。该查询将按降序返回最多 10 行。在您的应用程序中,搁置最后一行的id,并在下次运行查询时将其值重新用作:last_lowest_id,然后重复(再次,搁置id最新查询返回的最后一行等。)这实质上会使查询增量且非常快。
例子:
- 第一次运行查询,
:last_lowest_id 设置为 0
- 返回 10 行 ID:
129, 100, 99, 88, 83, 79, 78, 75, 73, 70
- 保存
70
- 第二次运行查询,
:last_lowest_id 设置为70
- 返回 10 行 ID:
130, 129, 100, 99, 88, 83, 79, 78, 75, 73
- 保存
73
- 等
方法 4 - 另一种方法
如果您希望在comments 表中执行SELECT ... ORDER BY id DESC LIMIT 10 比INSERTs 更频繁,请考虑在INSERT 中添加更多工作以使SELECT 更快。因此,您可以将索引的updated_at 列添加到您的topics 等表中,并且每当您INSERT 对comments 表进行评论时,请考虑将相应主题的updated_at 值更新为NOW()。然后,您可以轻松选择 10 个最近更新的主题(对 updated_at 进行简单而简短的索引扫描,返回 10 行),与 comments 表进行内部连接以获得这 10 个主题的 MAX(id)(比为 所有 主题获取 MAX(id),然后再选择 10 个最大的主题,就像在原始主题和方法 1 中一样),然后再次在 comments 上进行内部连接以获取这 10 个的其余列值。 p>
我希望方法 4 的整体性能可以与方法 2 和 3 相媲美。如果您需要获取任意主题(例如,通过对它们进行分页,LIMIT 10 OFFSET 50)或者如果主题或 cmets 可以已删除(无需更改即可支持主题删除;为了支持正确删除评论,则应在评论 INSERT 和 DELETE 上更新主题的 updated_at,并使用该主题的最新未删除评论的 created_at 值.)