我制作了一个简单的数据库(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]。
以下查询在~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以防它有用。
非常感谢能够提供帮助的任何人。
答案 0 :(得分:1)
如果有"其他约束",则所有投注均已关闭。
同时,看看你有什么......
STRAIGHT_JOIN
,USE INDEX
等等,是因为(a)你没有“权利”这样的权利。索引,或(b)优化器无法弄清楚'权利'要做的事。 也就是说,寻找其他解决方案。
在您的示例中,使用普通JOIN
和INDEX(tag_id, post_id)
会更好。这将允许它首先转到post_tag
,因为有一个WHERE
子句让它在那里过滤。优化程序可能会看到t.post_id
和p.id
相同,因此请在索引中开始DESC
的{{1}}}结束,然后扫描。然后它检查是否有(25, post_id)
条目(这是post
唯一明显的用途 - 再次,如果有"其他约束",所有投注都关闭)。
所以,回到最初的问题。 post
强制首先查看STRAIGHT_JOIN
。但25岁的人在哪里?显然位于post
的 end 附近。因此,post_tag
需要更长时间才能找到100(参见ASC
),而不是扫描从另一端开始!
假设这是一个多对多映射表,请执行以下操作:
LIMIT
我在my blog中讨论了很多原因。
如果按照建议添加CREATE TABLE post_tag (
post_id ...,
tag_id ...,
PRIMARY KEY(post_id, tag_id),
INDEX (tag_id, post_id)
) ENGINE=InnoDB;
,请不要嘲笑(tag_id, post_id DESC)
意味着什么 - 它会被识别,但会被忽略。 两个部分都将存储DESC
。会发生什么是优化器足够聪明,可以在25秒结束时开始向后扫描。这里"证明":
ASC
有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
声明中忽略DESC
的MySQL错过了唯一的情况是:INDEX
无法使用ORDER BY a ASC, b DESC
。
答案 1 :(得分:0)
据推测,您在post(id)
上有一个索引(例如,这是为主键自动创建的)。当使用ORDER BY
的索引时,MySQL有时会关注索引的顺序。
通过更改顺序,您将更改查询计划,以便进行必要的排序。
我建议只使用一个表来编写查询:
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可能会为降序排序做正确的事。