MySQL DESC 顺序查询比 ASC 顺序查询更快

MySQL query faster in DESC order than ASC order

我做了一个简单的数据库(innodb 版本 5.7.9),有 2 个表,post 和 post_tag。

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

以下查询在 ~1 毫秒内运行:

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 以防有用。

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

解释如下:

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

通过更改顺序,您正在以需要排序的方式更改查询计划。

我建议只使用一个 table:

来编写查询
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 可能适合降序排序。

如果有"other constraints",则所有投注均无效。

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

STRAIGHT_JOINUSE INDEX 等是以下情况的拐杖:(a) 您没有 'right' 索引,或 (b) 优化器无法找出'right' 要做的事情。 也就是寻找其他方案

在您的示例中,您最好使用普通的 JOININDEX(tag_id, post_id)。这会让它转到 post_tag first 因为有一个 WHERE 子句让它在那里过滤。优化器可能会发现 t.post_idp.id 相同,因此在索引中从 (25, post_id) 的结尾(对于 DESC)开始,然后扫描。然后它会检查是否有 post 条目(这是 post 的唯一明显用途——再次如果有 "other constraints",则所有投注均无效)。

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

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

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 秒结束时开始并向后扫描。这是 "proof":

USINDEX(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)