【问题标题】:MySQL query faster in DESC order than ASC orderMySQL 以 DESC 顺序查询比 ASC 顺序更快
【发布时间】:2016-05-09 00:20:54
【问题描述】:

我制作了一个简单的数据库(innodb 版本 5.7.9),其中包含 2 个表,post 和 post_tag。

Post 将单个字段 ID(大整数)设置为主键(大约 120,000 个条目)。 post_tag有2个字段,post_id(big int)和tag_id(int),主键在[post_id, tag_id]上。

以下查询在 ~1ms 内运行:

SELECT 
   SQL_NO_CACHE p.id 
FROM 
   post as p 
STRAIGHT_JOIN 
   post_tag t
WHERE  
   t.post_id = p.id AND t.tag_id = 25 
ORDER BY 
   p.id DESC
LIMIT 0, 100

但如果我将 ORDER BY 更改为 ASC,它的运行速度会慢 100 倍!还有我感兴趣的那种……

知道为什么吗?

最初,我希望将 ID 排序为 DESC,但我发现它比 ASC 慢。我读到索引的自然排序是 ASC,所以我恢复了所有 ID(通过 ID = SOMETHING BIG - ID),但它没有改变任何东西,因为它现在在 ASC 中变慢了。

我上传了数据库here,以防它有用。

非常感谢任何可以提供帮助的人。

这里是解释:

【问题讨论】:

  • WHERE 更改为ON 并且只是执行常规的JOIN 而不是STRAIGHT_JOIN 会改变什么吗?
  • 您可能在表上创建了一个索引,或者可能在 ID 列上默认创建了一个默认主键索引。您可以重新索引它并使其成为您想要的顺序。如果您以与索引顺序相反的顺序查询,则查询肯定会运行得更慢。只需尝试按照您要查询的顺序重新索引即可。
  • @JoachimIsaksson 将位置更改为 ON 不会改变任何内容。进行常规连接将执行计划更改为从 post_tag 表开始,这不是我想要的。原因是我的实际数据库(我在这里发布的只是一个精简的示例)有一个更复杂的索引键,需要通过 post 表启动执行计划。实际上,在这种情况下,我不需要直接加入,因为 mysql 明白这一点。因此,我只是将直接连接置于与我的实际数据库相同的条件下。

标签: mysql performance sorting innodb


【解决方案1】:

如果有“其他限制”,那么所有的赌注都没有。

同时,看看你有什么......

STRAIGHT_JOINUSE INDEX 等是当 (a) 您没有“正确”索引,或 (b) 优化器无法找出“正确”要做的事情时的拐杖。 也就是说,寻找其他解决方案。

在您的示例中,您最好使用普通的JOININDEX(tag_id, post_id)。这将让它转到post_tag first,因为有一个WHERE 子句让它在那里过滤。优化器可能会看到t.post_idp.id 是相同的,因此在索引中开始(25, post_id) 的结尾(对于DESC),然后扫描。然后它会检查是否有 post 条目(这是 post 的唯一明显用途——同样,如果有“其他限制”,则所有赌注都关闭)。

那么,回到最初的问题。 STRAIGHT_JOIN 强制首先查看post。但是25岁在哪里?显然在post_tagend 附近。因此,ASC 找到 100 个(参见 LIMIT)的时间比从另一端开始扫描的时间要长!

假设这是一个多对多映射表,请执行以下操作:

CREATE TABLE post_tag (
    post_id ...,
    tag_id ...,
    PRIMARY KEY(post_id, tag_id),
    INDEX      (tag_id, post_id)
) ENGINE=InnoDB;

我在my blog讨论了很多原因。

如果按照建议添加(tag_id, post_id DESC),请不要误以为DESC 意味着什么——它被识别,但被忽略。 两个部分都将存储在ASC。将会发生的事情是优化器足够聪明,可以在 25 秒结束时开始并向后扫描。这是“证明”:

US 拥有INDEX(state, population)

mysql> FLUSH STATUS;
mysql> SELECT city, population FROM US
          WHERE state = 'OH'
          ORDER BY population DESC LIMIT 5;
+------------+------------+
| city       | population |
+------------+------------+
| Columbus   |     736836 |
| Cleveland  |     449514 |
| Toledo     |     306974 |
| Cincinnati |     306382 |
| Akron      |     208414 |
+------------+------------+
mysql> SHOW SESSION STATUS LIKE 'Handler%';
| Handler_read_key           | 1     |  -- get started at end of Ohio
| Handler_read_prev          | 4     |  -- read (5-1) more, scanning backwards

MySQL 通过在INDEX 声明中忽略DESC 而遗漏的唯一情况是:ORDER BY a ASC, b DESC 不能使用INDEX(a,b)

【讨论】:

  • 您完全正确,25 位在列表的末尾,这就解释了为什么按顺序执行比另一个需要更长的时间。非常感谢!
【解决方案2】:

大概,您在post(id) 上有一个索引(例如,这是为主键自动创建的)。 MySQL在使用ORDER BY的索引时有时会注意索引的顺序。

通过更改顺序,您正在更改查询计划,从而需要进行排序。

我建议只使用一个表来编写查询:

SELECT t.post_id 
FROM post_tag t
WHERE t.tag_id = 25 
ORDER BY t.post_id DESC
LIMIT 0, 100;

JOIN 对于此查询不是必需的,假设 post_id 的所有值都引用有效帖子(这似乎是一个非常合理的假设)。

对于这个查询,post_tag(tag_id, post_id desc) 上的索引是最佳的,MySQL 可能会为降序排序做正确的事情。

【讨论】:

  • 正如我所解释的,我在 post(id) 中有一个主索引。感谢您的建议,但我想通过了解为什么它在 ASC 中比 DESC 慢来解决问题,而不是更改查询。原因是我无法将您的建议应用到我的真实数据库中,该数据库有其他限制。
  • 但是您建议的查询非常有趣,因为它在 DESC(~1ms)中比在 ASC 顺序(~10ms)中快大约 10 倍。知道为什么吗?
  • @maalls 。 . .这可能只是向后读取索引的一些微妙的低效率。时间非常小,所以 10 倍的差异可能没有意义。如此小的差异可能是由缓存或前瞻页面读取优化等因素造成的。
  • @maalls 。 . .您应该更改查询。摆脱不必要的连接是一件好事。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-14
  • 1970-01-01
  • 1970-01-01
  • 2013-04-13
  • 2012-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多