您好我写了一个有效的查询:
SELECT `comments`.* FROM `comments`
RIGHT JOIN (SELECT MAX( id ) AS id, core_id, topic_id
FROM comments GROUP BY core_id, topic_id order by id desc) comm
ON comm.id = comments.id LIMIT 10
我想知道是否有可能(以及如何)重写它以获得更好的性能。
由于
答案 0 :(得分:5)
我很确定在这种情况下 INNER JOIN
就足够了,没有理由做RIGHT JOIN
(如果id
存在comm
它也会存在comments
)。 INNER JOIN
s 可能会产生better performance。
此外,你真的想推动LIMIT 10
内comm
(顺便提一下,与ORDER BY
保持一致):
LIMIT 10
和ORDER BY
放在一起将不为您提供最近发布的十个主题(排序comm
子查询不一定会保留到LIMIT
的最终结果中。)LIMIT
将鼓励基于成本的优化器优先于nested loops或hash merge joins(确切地说是10) (对于任何大小合适的comments
表,10个嵌套循环到目前为止最快。)因此,您的查询应该重写为:
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
嵌套循环。
请注意,虽然上述查询仍然可能比原始查询更快,但最里面的comm
子查询可能仍然会成为一个重大瓶颈,如果它导致全表扫描comments
。这实际上取决于数据库在看到GROUP BY
,ORDER BY
和LIMIT
时的智能程度。
如果EXPLAIN
表示子查询正在进行表扫描,那么您可以尝试 SQL和应用程序级逻辑的组合以获得最佳性能假设我已正确理解您的要求,并且您希望确定在十个不同主题中发布的十条最新评论:
# 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
返回控件之前没有尝试将所有行缓冲到内存中),则上面只会获取少量行,总是使用索引,不涉及表扫描。
如果十秒前我知道最近的十条评论是什么,当我稍后再问这个问题时,我能否明智一点? 除非可以从数据库中删除评论,否则答案是肯定的,因为我知道,当我再次提出问题时,所有评论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
129, 100, 99, 88, 83, 79, 78, 75, 73, 70
70
:last_lowest_id
设置为70
130, 129, 100, 99, 88, 83, 79, 78, 75, 73
73
如果您希望SELECT ... ORDER BY id DESC LIMIT 10
比INSERT
表更频繁地执行comments
,请考虑在INSERT
中加入更多工作来制作SELECT
updated_at
1}}更快。因此,您可以在topics
等表格中添加已编入索引的INSERT
列,并且只要comments
对updated_at
表发表评论,也请考虑更新相应主题的NOW()
1}}值为updated_at
。然后,您可以轻松选择10个最近更新的主题(comments
返回10行的简单和短索引扫描),内部加入MAX(id)
表以获取这些主题的MAX(id)
主题(在选择十个最好的主题之前获得所有主题的comments
效率更高,比如在原始和方法1中),然后再次在LIMIT 10 OFFSET 50
上进行内部联接以获得那些10的其余列值。
我希望方法4的整体性能与方法2和3相当。如果您需要获取任意主题(例如,通过对它们进行分页,updated_at
)或主题或评论,则必须使用方法4可以删除(不支持删除主题所需的更改;为了正确支持删除评论,应该使用INSERT
值更新评论DELETE
和created_at
上的主题{{1}}该主题的最新未删除评论。)