我有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 |
+----+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
答案 0 :(得分:15)
这些是了解此行为的相关事实:
您正在使用InnoDB,它使用Clustered Index概念。
对于您的特定情况,聚簇索引的唯一有趣的副作用是每个非主键索引还将隐式包含主键作为索引中的最后一列。没有(owner_id, post_id)
上的索引 - 你已经拥有它了。
MySQL无法以正确的方式解析非前导索引列上的范围条件(&lt;,&gt;)。相反,它将在索引查找期间忽略它们,稍后将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/是我的指南,解释了索引的工作原理。
答案 1 :(得分:1)
没有什么,因为“使用索引条件”,但MySQL如何使用INDEX及其查询引擎。 MySQL使用简单的查询分析器和优化器。
如果是post_id > 900000 and owner_id = 20
,您可能会注意到它尝试使用键my_fk
,这是一个&#34; BIGGER INDEX&#34;因为它的大小为(64 + 32)*行。它从索引中找到所有owner_id = 20
(是的,没有使用post_id。愚蠢的mysql)
在MySQL使用BIG和HEAVIER索引找到你需要的所有行之后,它会进行另一次查找以通过主键读取实际行(因为你做SELECT *
),(这里有更多的HDD寻找),并使用post_id > 900000
(SLOW)
在order by post_id desc
的情况下,运行速度更快可能有很多原因。一个可能的原因是InnoDB缓存,插入最少的行比其他行更温暖,更容易访问。
在post_id > 900000 and owner_id > 19 and owner_id < 20
的情况下,MySQL放弃my_fk
作为辅助索引的范围扫描并不比主索引上的范围扫描更好。
如果您的InnoDB页面没有碎片,只需使用PK找到post_id 900000的正确页面,并从那里执行 SEQUENCE READ 。 (假设您正在使用AUTO_INCREMENT)扫描一些页面,并过滤符合您需要的内容。
进行&#34;优化&#34;,(现在就做):不要使用SELECT *
进行&#34;过早优化&#34; (不要这样做;不要这样做);通过USE INDEX
提示MySQL;创建索引包含您需要的所有列。
很难说哪个更快,my_fk
和PK
。因为数据模式的性能各不相同。如果owner_id = 20在您的表中占主导地位或常见,则直接使用PK可能会更快。
如果您的表格中不存在owner_id = 20,则my_fk
会提升,因为要读取的行数太多(post_id&gt; 900000 + XXX)。
-
编辑:BTW,尝试ORDER BY owner_id ASC, post_id ASC
或DESC。如果只能使用INDEX的顺序(而不是命令索引),MySQL会更快。
答案 2 :(得分:0)
我不是MySQL专家,但我不认为任何一个查询都在使用索引 - 除非您创建的索引尚未告诉我们。在&#39;使用索引条件&#39;可能是MySQL实现LIMIT关键字的方式的假象。
如果您将一个由(owner_id,post_id)组成的索引放在帖子表上,它将有助于这两个查询。在MySQL中它应该看起来像:
create index ix_post_userpost on post (owner_id, post_id)
(我不保证语法,因为我没有MySQL。)