use*_*171 7 mysql temporary-tables
我有两张桌子 -comments
和votes
:
comments(id, text, user_id, page_id)
votes(id, value)
Run Code Online (Sandbox Code Playgroud)
表中有 2,000,000 行votes
。
我创建了以下查询:
SELECT SUM(votes.value),
comments.text,
comments.comment_id
FROM comments, votes
WHERE comments.comment_id = votes.comment_id AND comments.page_id = $page_id
GROUP BY comment_id;
Run Code Online (Sandbox Code Playgroud)
查询运行了大约 12 秒,这正常吗?
SHOW PROCESSLIST
说它复制到临时表。这看起来很慢,是否需要使用临时表?
Rol*_*DBA 13
在 RDBMS 的世界中,临时表是生活中的事实。它只会在 JOINs 中抬起丑陋的头。
即使是最坏的 JOIN 情况也是退化的 JOIN,查询一个表。
由于临时表总是进入我们的查询(进入我们的生活),所以你能做的最好的事情就是饿死临时表。使它们尽可能紧凑。我什么意思???
这是您的查询:
SELECT SUM(votes.value),
comments.text,
comments.comment_id
FROM comments, votes
WHERE comments.comment_id = votes.comment_id AND comments.page_id = $page_id
GROUP BY comment_id;
Run Code Online (Sandbox Code Playgroud)
您的查询实际上将创建一个表,该表由评论和投票的联接产生,其行数是评论数乘以 2,000,000。由于临时表没有索引,因此临时表总是会发生笛卡尔连接。一路应用 WHERE 子句,然后应用 GROUP BY 求和。不要忘记,临时表还将包含文本列。在 JOIN 阶段需要拖动大量文本数据。
让我们重构您的查询
可以这么说,您可以在通行证处关闭 WHERE 子句。方法如下:
SELECT comment_id FROM comments WHERE page_id = $page_id;
Run Code Online (Sandbox Code Playgroud)
这个查询只有来自comments 表的必要键。
接下来,从投票中收集comment_ids
SELECT comment_id,SUM(value) sumofvalues FROM votes;
Run Code Online (Sandbox Code Playgroud)
这实际上是最糟糕的部分。每个 comment_id 有 200 万行和 4 个字节,总和有 4 个字节,在绝对最坏的情况下,这是一个 16MB 的表。
接下来,将评论的键与投票中的匹配键结合起来。
SELECT BB.* FROM
(SELECT comment_id FROM comments WHERE page_id = $page_id) AA
INNER JOIN
(SELECT comment_id,SUM(value) sumofvalues FROM votes GROUP BY comment_id) BB
USING (comment_id);
Run Code Online (Sandbox Code Playgroud)
既然检索了来自评论的键和来自投票的值的总和,最后一部分是将评论 ID 连接回原始评论表并获取文本字段。
SELECT
B.sumofvalues,A.text,A.comment_id
FROM
comments A INNER JOIN
(
SELECT BB.*
FROM
(SELECT comment_id FROM comments WHERE page_id = $page_id) AA
INNER JOIN
(SELECT comment_id,SUM(value) sumofvalues
FROM votes GROUP BY comment_id) BB
USING (comment_id)
) B
USING (comment_id);
Run Code Online (Sandbox Code Playgroud)
在这个重构的查询尽可能快地工作之前,您需要正确的索引。
以下是您需要的索引:
ALTER TABLE comments ADD INDEX pageid_commentid_ndx (page_id,comment_id);
ALTER TABLE votes ADD INDEX commentid_value_ndx (comment_id,value);
Run Code Online (Sandbox Code Playgroud)
您需要第一个索引,因为它将按 page_id 对行进行分组。您需要第二个索引,因为它会按 comment_id 对行进行分组。事实上,这两个索引都称为覆盖索引。为什么这很重要???这很重要,因为子查询只会从索引中检索所需的数据,从不接触主表。当所有需要的键在子查询中编译在一起时,comments 表只被访问一次。
试一试 !!!
如果任何语法不起作用,请对问题发表评论并告诉我!!!
假设这是 MySQL,您可以使用EXPLAIN
语法来帮助确定查询是如何运行的:
EXPLAIN SELECT SUM(votes.value),comments.text,comments.comment_id FROM comments,votes
WHERE comments.comment_id = votes.comment_id AND comments.page_id = $page_id
GROUP BY comment_id;
Run Code Online (Sandbox Code Playgroud)
第一个猜测是您将需要在您加入投票表的列上建立索引。