我有以下MySQL表(简化):
CREATE TABLE `track` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(256) NOT NULL,
`is_active` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
KEY `is_active` (`is_active`, `id`)
) ENGINE=MyISAM AUTO_INCREMENT=7495088 DEFAULT CHARSET=utf8
Run Code Online (Sandbox Code Playgroud)
'is_active'列标记了我想在大多数但不是全部查询中忽略的行.我有一些查询定期从这个表中读取块.其中一个看起来像这样:
SELECT id,title from track where (track.is_active=1 and track.id > 5580702) ORDER BY id ASC LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
此查询需要一分钟才能执行.这是执行计划:
> EXPLAIN SELECT id,title from track where (track.is_active=1 and track.id > 5580702) ORDER BY id ASC LIMIT 10;
+----+-------------+-------+------+----------------+--------+---------+-------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+----------------+--------+---------+-------+---------+-------------+
| 1 | SIMPLE | t | ref | PRIMARY,is_active | is_active | 1 | const | 3747543 | Using where |
+----+-------------+-------+------+----------------+--------+---------+-------+---------+-------------+
Run Code Online (Sandbox Code Playgroud)
现在,如果我告诉MySQL忽略'is_active'索引,则查询会立即发生.
> EXPLAIN SELECT id,title from track IGNORE INDEX(is_active) WHERE (track.is_active=1 AND track.id > 5580702) ORDER BY id ASC LIMIT 10;
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
| 1 | SIMPLE | t | range | PRIMARY | PRIMARY | 4 | NULL | 1597518 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
Run Code Online (Sandbox Code Playgroud)
现在,真正奇怪的是,如果我强迫MySQL使用'is_active'索引,那么查询会立即再次发生!
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
| 1 | SIMPLE | t | range | is_active |is_active| 5 | NULL | 1866730 | Using where |
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
Run Code Online (Sandbox Code Playgroud)
我只是不明白这种行为.在'is_active'索引中,行应按is_active排序,后跟id.我在查询中使用了'is_active'和'id'列,所以看起来它应该只需要在树周围做一些跳来找到ID,然后使用这些ID从表中检索标题.
这是怎么回事?
编辑:有关我正在做的更多信息:
看起来MySQL在如何使用索引方面做出了糟糕的决定.
从该查询计划中,它显示它可以使用PRIMARY或is_active索引,并且它已选择is_active以便首先通过track.is_active缩小.但是,它仅使用索引的第一列(track.is_active).这得到3747543结果,然后必须进行过滤和排序.
如果当初选择的主要指标,它能够缩小到使用索引1597518行,他们会为了track.id已的,应该不需要进一步排序检索.那会更快.
新的消息:
在使用FORCE INDEX的第三种情况下,MySQL使用的是is_active索引,但现在不是仅使用第一列,而是使用两列(请参阅key_len).因此,它现在能够通过is_active缩小并使用相同的索引按id排序和过滤,并且因为is_active是单个常量,所以第二列满足ORDER BY(即索引的单个分支中的行已经是按排序顺序).这似乎比使用PRIMARY更好 - 甚至可能是你想要的,对吧?
我不知道为什么没有FORCE INDEX就不使用这个索引的两列,除非查询在两者之间以微妙的方式发生了变化.如果不是,我会把它归结为MySQL做出糟糕的决定.