[英]ORDER BY … ASC is slow and “Using index condition”
我有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语句:
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,它使用Clustered Index概念。 对于您的特定情况,聚簇索引的唯一有趣的副作用是每个非主键索引还将隐式包含主键作为索引中的最后一列。 对于(owner_id, post_id)
的索引没有nedd - 你已经拥有了它。
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/是我的指南,解释了索引的工作原理。
这没什么,因为“使用索引条件”,但MySQL如何使用INDEX及其查询引擎。 MySQL使用简单的查询分析器和优化器。
在post_id > 900000 and owner_id = 20
的情况下,您可能会注意到它尝试使用密钥my_fk
,这是一个“BIGGER INDEX”,因为它的大小为(64 + 32)*行。 它从索引中找到所有owner_id = 20
(是的,post_id没用过owner_id = 20
mysql)
在MySQL使用BIG和HEAVIER索引来定位您需要的所有行之后,它会执行另一次查找以通过其主键读取实际行(因为您执行SELECT *
)(此处有更多HDD寻找),并使用以下内容过滤结果post_id > 900000
(慢)
在order by post_id desc
的情况下,它运行得更快可能有很多原因。 一个可能的原因是InnoDB缓存,插入最少的行比其他行更温暖,更容易访问。
在post_id > 900000 and owner_id > 19 and owner_id < 20
,MySQL放弃my_fk
作为辅助索引上的远程扫描并不比主索引上的远程扫描更好。
它只是使用PK来找到post_id 900000的正确页面,如果您的InnoDB页面没有碎片,则从那里进行SEQUENCE READ 。 (假设您正在使用AUTO_INCREMENT)扫描一些页面,并过滤符合您需要的内容。
要做“优化”,(现在就做):不要使用SELECT *
做一个“过早优化”(不要这样做;不要这样做); 通过USE INDEX
提示MySQL; 创建索引包含您需要的所有列。
很难说哪个更快, my_fk
和PK
。 因为数据模式的性能各不相同。 如果owner_id = 20在您的表中占主导地位或常见,则直接使用PK可能会更快。
如果owner_id = 20在您的表中不常见, my_fk
将会提升,因为要读取的行数太多(post_id> 900000 + XXX)。
- 编辑:BTW,尝试ORDER BY owner_id ASC, post_id ASC
或DESC。 如果只能使用INDEX的顺序(而不是命令索引),MySQL会更快。
我不是MySQL专家,但我不认为任何一个查询都使用索引 - 除非您创建的索引没有告诉我们。 在'使用索引条件'可能是MySQL实现LIMIT关键字的方式的假象。
如果您将一个由(owner_id,post_id)组成的索引放在帖子表上,它将有助于这两个查询。 在MySQL中它应该看起来像:
create index ix_post_userpost on post (owner_id, post_id)
(我不保证语法,因为我没有MySQL。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.