防止复制到临时表 (sql)

use*_*171 7 mysql temporary-tables

我有两张桌子 -commentsvotes

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 表只被访问一次。

试一试 !!!

如果任何语法不起作用,请对问题发表评论并告诉我!!!


Der*_*ney 7

假设这是 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)

第一个猜测是您将需要在您加入投票表的列上建立索引。