ORDER BY ... ASC很慢并且"使用索引条件"

Jac*_*ood 3 mysql sql database indexing sql-order-by

我有2张桌子:userpost.

使用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;
Run Code Online (Sandbox Code Playgroud)

一切都很好我用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)
Run Code Online (Sandbox Code Playgroud)

然后我使用explain语句:

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)
Run Code Online (Sandbox Code Playgroud)

我认为重点是,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 |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
Run Code Online (Sandbox Code Playgroud)

Mar*_*and 15

这些是了解此行为的相关事实:

  • 您正在使用InnoDB,它使用Clustered Index概念.对于您的特定情况,聚簇索引的唯一有趣的副作用是每个非主键索引还将隐式包含主键作为索引中的最后一列.没有索引(owner_id, post_id)- 你已经拥有了它.

  • MySQL无法以正确的方式解决非前导索引列上的范围条件(<,>).相反,它将在索引查找期间忽略它们,稍后将where子句的这一部分应用为过滤器.这只是一个MySQL限制,不能直接在post_id = 900000其他位置开始扫描- 其他数据库这样做非常好.

  • 当您使用DESC订单时,MySQL将开始读取post_id它找到的最大值的索引.然后它将应用您的过滤器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/是我的指南,解释了索引的工作原理.