带有“order by”的派生表使用临时表和文件排序,即使我只选择主键

Posted

技术标签:

【中文标题】带有“order by”的派生表使用临时表和文件排序,即使我只选择主键【英文标题】:Derived table with "order by" uses temporary table and filesort, even though I'm only selecting primary key 【发布时间】:2015-03-26 19:49:51 【问题描述】:

有一个包含表格的论坛:帖子、主题、论坛、用户。

我正在尝试列出最近 30 个帖子以及其他表中的相关数据,以及帖子所在主题中的帖子数。

这是我使用的查询:

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 语句,文件排序和临时表就会消失,即使它不应该改变它(我猜)。

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 来避免文件排序和临时表。

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

我的问题是:

哪个查询在性能方面更胜一筹(两者的效果相同) 如果第一个更好,我怎样才能摆脱文件排序和临时表(我什至应该吗?还是可以,只是优化器的副作用?)

谢谢大家!

【问题讨论】:

尽管 mysql 允许这种分组方式,但在任何情况下你都不应该使用它。您应该按照 SQL 标准中的预期方式使用 group,即列出除聚合字段之外的所有字段。做任何其他事情都会产生不一致且通常不正确的结果。这是你需要改掉的坏习惯。 编辑了帖子。这是你的意思吗?感谢您的回复! 您是否阅读了GROUP BY Optimization 的 MySQL 文档? “对 GROUP BY 使用索引最重要的前提条件是所有 GROUP BY 列都引用同一索引中的属性,并且索引按顺序存储其键......” 所以整个查询是坏的? :3 我应该从头开始重写它,还是第三个好? 为什么人们仍然认为一个有大量连接的查询比几个小查询更有效?仅仅因为几>一?尝试分解您的查询。要检查哪个查询更有效,请使用 benchmark。 【参考方案1】:

您的第三个查询很好,并且比前两个简单得多。但是,我不确定为什么需要使用 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 表的稀疏性;因此,我在那里添加了更多条目,因为之前使用 INNER JOIN 会导致不返回任何结果。

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

选项 1

将所有表从 MyISAM 更改为 InnoDB

选项 2

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

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

选项 3

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

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

推理

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

如果您愿意,我可以详细说明这些索引的“原因”。

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

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

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

以下查询使用索引来解析 ORDER BY 部分:

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

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

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

您正在连接许多表,并且 ORDER BY 中的列不是 全部来自用于检索行的第一个非常量表。 (这是 EXPLAIN 输出中第一个没有 const 连接类型。)

我们正在加入表,所以我们绝对需要考虑那个表,以及如何确保 posts 表是第一个。

用于获取行的键与在 订购人

好的,所以我们需要确保我们使用的是相同的密钥。我们如何做到这一点?

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

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

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

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

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

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

【讨论】:

@sugarfree 我已经更新了我的答案,看看这些更改是否有帮助,是否需要更多信息。 第二个选项为我解决了这个问题,但我不明白为什么。我似乎根本没有使用新索引。我将您的答案标记为已接受,但很想知道它是如何以及为什么解决它的。 @sugarfree 我已经更新了答案并提供了进一步的解释。

以上是关于带有“order by”的派生表使用临时表和文件排序,即使我只选择主键的主要内容,如果未能解决你的问题,请参考以下文章

使用Union All 和Order By In View?

ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式中无效

BigQuery Storage API 无法读取由有序 (ORDER BY) 查询创建的临时表

ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式中无效,除非 TOP、OFFSET 或 FOR XML

sqlserver 创建视图失败,原因:ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式中无效

高手们 为啥数据库查询语句加了order by 变得很慢 表中有六千多条记录 现在打开很慢 啥原因