我有 2 个表:user
和 post
。
使用 show create table 语句:
CREATE TABLE `user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_name` varchar(20) CHARACTER SET latin1 NOT NULL,
`create_date` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=utf8;
CREATE TABLE `post` (
`post_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`owner_id` bigint(20) NOT NULL,
`data` varchar(300) CHARACTER SET latin1 DEFAULT NULL,
PRIMARY KEY (`post_id`),
KEY `my_fk` (`owner_id`),
CONSTRAINT `my_fk` FOREIGN KEY (`owner_id`) REFERENCES `user` (`user_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1012919 DEFAULT CHARSET=utf8;
一切都很好,我用 ORDER BY 语句执行了 2 个查询,结果很奇怪,ASC
很慢,但是 DESC
很快。
SELECT sql_no_cache * FROM mydb.post where post_id > 900000 and owner_id = 20 order by post_id desc limit 10;
10 rows in set (0.00 sec)
SELECT sql_no_cache * FROM mydb.post where post_id > 900000 and owner_id = 20 order by post_id asc limit 10;
10 rows in set (0.15 sec)
然后我使用解释语句:
explain SELECT sql_no_cache * FROM mydb.post where post_id > 900000 and owner_id = 20 order by post_id desc limit 10;
+----+-------------+-------+------+---------------+-------+---------+-------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-------+---------+-------+--------+-------------+
| 1 | SIMPLE | post | ref | PRIMARY,my_fk | my_fk | 8 | const | 239434 | Using where |
+----+-------------+-------+------+---------------+-------+---------+-------+--------+-------------+
1 row in set (0.01 sec)
explain SELECT sql_no_cache * FROM mydb.post where post_id > 900000 and owner_id = 20 order by post_id asc limit 10;
+----+-------------+-------+------+---------------+-------+---------+-------+--------+------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-------+---------+-------+--------+------------------------------------+
| 1 | SIMPLE | post | ref | PRIMARY,my_fk | my_fk | 8 | const | 239434 | Using index condition; Using where |
+----+-------------+-------+------+---------------+-------+---------+-------+--------+------------------------------------+
1 row in set (0.00 sec)
我认为重点是 Using index condition
但我不知道为什么。如何改进我的数据库以获得更好的性能?
更新:
explain SELECT * FROM mydb.post where post_id < 600000 and owner_id = 20 order by post_id desc limit 10;
+----+-------------+-------+------+---------------+-------+---------+-------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-------+---------+-------+--------+-------------+
| 1 | SIMPLE | post | ref | PRIMARY,my_fk | my_fk | 8 | const | 505440 | Using where |
+----+-------------+-------+------+---------------+-------+---------+-------+--------+-------------+
explain SELECT * FROM mydb.post where post_id < 600000 and owner_id > 19 and owner_id < 21 order by post_id desc limit 10;
+----+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
| 1 | SIMPLE | post | range | PRIMARY,my_fk | PRIMARY | 4 | NULL | 505440 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
最佳答案
这些是理解此行为的相关事实:
您正在使用 InnoDB,它使用了聚簇索引概念。 对于您的特定情况,聚集索引的一个有趣的副作用是每个非主键索引也将隐式包含主键作为索引中的最后一列。不需要
(owner_id, post_id)
上的索引— 你已经拥有了。MySQL 无法以正确的方式解析非前导索引列上的范围条件(<、>)。相反,它只会在索引查找期间忽略它们,然后将 where 子句的这一部分用作过滤器。这只是 MySQL 的一个限制,不能直接在
post_id = 900000
的位置开始扫描。 — 其他数据库在这方面做得很好。当您使用
DESC
时命令,MySQL 将开始读取最大的索引post_id
它发现的值(value)。然后它将应用您的过滤器post_id > 900000
.如果匹配,则返回该行。然后它继续到下一行,依此类推,直到找到 10 个匹配的行。但是,所有匹配行都保证位于索引扫描开始的位置。当您使用
ASC
时命令,MySQL 开始读取另一端的索引,对照post_id > 900000
检查这个值并且可能需要丢弃该行,因为post_id
低于该阈值。现在猜猜在找到与post_id > 900000
匹配的第一行之前需要以这种方式处理多少行?这就是占用您时间的原因。“使用索引条件”是指索引条件下推:http://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html我会说它应该适用于这两种情况。但是,它在 DESC 情况下并不那么相关,因为过滤器无论如何都不会删除任何行。在 ASC 情况下,它非常相关,没有它性能会最差。
如果你不想验证我的陈述,你可以
增加/减少数值 (900000) 并查看性能如何变化。较低的值应该使
ASC
在保持DESC
的同时更快也快。改变范围条件
>
至<
看看它是否逆转了ASC
的性能行为/DESC
.请记住,您可能需要将数字更改为某个较低的值才能真正看到性能差异。
人们怎么可能知道这一点?
http://use-the-index-luke.com/是我的指南,它解释了索引的工作原理。
关于mysql - ORDER BY ... ASC 很慢, "Using index condition",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24176540/