使用 "order by" 派生的 table 使用临时 table 和文件排序,即使我只选择主键

Derived table with "order by" uses temporary table and filesort, even though I'm only selecting primary key

有一个论坛包含 tables:posts、主题、论坛、用户。

我正在尝试列出最近 30 post 与其他 table 的相关数据,以及 post 所在主题中的 post 数量=] 位于.

这是我使用的查询:

SELECT t.id, t.name, t.permissions, t.author, t.added, COUNT(p2.id) pcount, u2.username pusername, u2.id pauthor, p.added padded, p.id pid, u.username
FROM posts p
INNER JOIN (SELECT id FROM posts ORDER BY id DESC LIMIT 30) tmp ON tmp.id = p.id
INNER JOIN topics t ON t.id = p.topic
INNER JOIN users u ON t.author = u.id
INNER JOIN users u2 ON p.author = u2.id
INNER JOIN posts p2 ON p2.topic = t.id
GROUP BY id, name, permissions, author, added, pusername, pauthor, padded, pid, username

解释一下SQL:http://i.stack.imgur.com/kCb0J.png

如果我删除 GROUP BY 语句,文件排序和临时 table 就会消失,即使它不应该改变(我猜)。

SELECT t.id, t.name, t.permissions, t.author, t.added, u2.username pusername, u2.id pauthor, p.added padded, p.id pid, u.username
FROM posts p
INNER JOIN (SELECT id FROM posts ORDER BY id DESC LIMIT 30) tmp ON tmp.id = p.id
INNER JOIN topics t ON t.id = p.topic
INNER JOIN users u ON t.author = u.id
INNER JOIN users u2 ON p.author = u2.id
INNER JOIN posts p2 ON p2.topic = t.id

解释一下SQL:http://i.imgur.com/z3Xkqu2.png

我还有一个实现相同功能的查询,但我必须使用 LEFT JOIN 来避免文件排序和临时 table。

SELECT t.id, t.name, t.permissions, t.author, t.added, (SELECT COUNT(*) FROM posts WHERE topic = t.id) as pcount, u2.username as pusername, u2.id as pauthor, p.added as padded, p.id as pid, u.username
FROM posts p
LEFT JOIN topics t ON t.id = p.topic
LEFT JOIN users u ON t.author = u.id
LEFT JOIN users u2 ON p.author = u2.id
ORDER BY p.id DESC LIMIT 30

解释一下SQL:http://i.imgur.com/qQMjBIV.png

我的问题是:

谢谢大家!

你的第三个查询很好,比前两个简单得多。但是,我不确定您为什么需要使用 LEFT JOIN,也不知道为什么不使用 INNER JOIN 会导致文件排序。

SELECT t.id, t.name, t.permissions, t.author, t.added, (SELECT COUNT(*) FROM posts WHERE topic = t.id) as pcount, u2.username as pusername, u2.id as pauthor, p.added as padded, p.id as pid, u.username
FROM posts p
INNER JOIN topics t ON t.id = p.topic
INNER JOIN users u ON t.author = u.id
INNER JOIN users u2 ON p.author = u2.id
ORDER BY p.id DESC LIMIT 30

以上是对您的请求的直接、简单的查询。

如果您可以提供一个 sqlfiddle 使用 INNER JOIN 而不是 LEFT JOIN 导致的文件排序示例,那么我们可以对此进行调查。

提供 SQLFiddle 后更新

使用你的 sqlfiddle,我发现了一些有趣的行为和信息。在各种情况下,文件排序会出现,其他会导致它消失。

其中一个问题是 sqlfiddle 中 users table 的稀疏性;因此,我在那里添加了更多条目,因为以前使用 INNER JOIN 会导致不返回任何结果。

无论如何,有 3 个可能的修复,您必须将它们应用到您的真实数据集以确定您需要应用多少个。

选项 1

将所有 table 从 MyISAM 更改为 InnoDB

选项 2

如果无法更改 table 类型或不足够,请向 posts table 添加索引。

ALTER TABLE `posts`
ADD INDEX `id_topic_author_added_i` (`id`,`topic`,`author`,`added`);

选项 3

如果以上两个选项不可用或不足,请在users table中添加索引。

ALTER TABLE `users`
ADD INDEX `id_username_i` (`id`,`username`);

推理

索引和引擎的目标将其更改为允许查询单次访问 table。在 InnoDB 下,聚簇主键应该根据您的查询准确提供发生这种情况所需的索引。我不太熟悉 MyISAM,但至少在 sqlfiddle 中不起作用。

如果您愿意,我可以扩展 "why" 这些索引的帮助。

您还可以查看我的 sqlfiddle 应用了所有 3 个选项,并亲眼看看当您删除上述每个选项时会发生什么。

更新:为什么添加这些索引有效

首先,让我们从 documentation 中的一些内容开始,我们被告知将允许或不允许使用索引(如果不使用索引,您可能会改为使用文件排序):

The following queries use the index to resolve the ORDER BY part:

SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;

所以这意味着我们应该将 ORDER BY 列作为键的第一部分(也称为索引)。

关于允许使用索引的内容,这就是它所说的适用于此查询的所有内容。现在,什么会阻止索引发挥作用:

You are joining many tables, and the columns in the ORDER BY are not all from the first nonconstant table that is used to retrieve rows. (This is the first table in the EXPLAIN output that does not have a const join type.)

我们正在加入tables,所以我们肯定需要考虑那个,以及如何确保posts table是第一个。

The key used to fetch the rows is not the same as the one used in the ORDER BY

好的,所以我们需要确保我们使用的是同一个密钥。我们该怎么做?

嗯,通常最好的应对方法是创建所谓的覆盖索引。这意味着单个索引,其中包含您希望在 SELECT 语句中拥有的所有列。

如果您没有覆盖索引,那么查询最终会使用索引来查找记录,然后使用附加到所有索引的主键来查找主行(其中包含所有列),然后它具有所需的所有列值。然而,在这样做的过程中,它每行执行了 2 次查找,而这正是覆盖索引试图避免的。

因此,使用上面的选项 2 索引,您可以看到它是一个覆盖索引,因此可以对 posts table 进行一次查找。另外,因为 id 是第一个,所以我们满足上面的第一个条件。覆盖索引部分,并将用于与其他 table 连接的列放在首位(topicauthor),我们允许查询在转到 [=16= 之后进行这些连接] table(至少我认为这是正在发生的事情,我有点对这句话挥手。)因此,我们确保它是 EXPLAIN 中的第一个,因此避免了上面的第二个条件防止使用索引。

这就是索引起作用的原因。

现在,奇怪的是,如果您使用的是 InnoDB,那么行是围绕每个 table 的主键组织的,在所谓的聚集索引中。聚集索引实际上是所有非 TEXT 或 BLOB 列的覆盖索引。

所以,将引擎类型更改为 InnoDB 应该就足够了。至于为什么不是,那超出了我的知识范围,所以如果您仍然好奇,您将不得不为此提出一个新问题。