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_JOIN
、USE INDEX
等是以下情况的拐杖:(a) 您没有 'right' 索引,或 (b) 优化器无法找出'right' 要做的事情。 也就是寻找其他方案
在您的示例中,您最好使用普通的 JOIN
和 INDEX(tag_id, post_id)
。这会让它转到 post_tag
first 因为有一个 WHERE
子句让它在那里过滤。优化器可能会发现 t.post_id
和 p.id
相同,因此在索引中从 (25, post_id)
的结尾(对于 DESC
)开始,然后扫描。然后它会检查是否有 post
条目(这是 post
的唯一明显用途——再次如果有 "other constraints",则所有投注均无效)。
所以,回到最初的问题。 STRAIGHT_JOIN
强制先看post
。但是25岁的人在哪里?显然在 post_tag
的 end 附近。因此,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":
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)
。
我做了一个简单的数据库(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_JOIN
、USE INDEX
等是以下情况的拐杖:(a) 您没有 'right' 索引,或 (b) 优化器无法找出'right' 要做的事情。 也就是寻找其他方案
在您的示例中,您最好使用普通的 JOIN
和 INDEX(tag_id, post_id)
。这会让它转到 post_tag
first 因为有一个 WHERE
子句让它在那里过滤。优化器可能会发现 t.post_id
和 p.id
相同,因此在索引中从 (25, post_id)
的结尾(对于 DESC
)开始,然后扫描。然后它会检查是否有 post
条目(这是 post
的唯一明显用途——再次如果有 "other constraints",则所有投注均无效)。
所以,回到最初的问题。 STRAIGHT_JOIN
强制先看post
。但是25岁的人在哪里?显然在 post_tag
的 end 附近。因此,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":
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)
。