为什么在MySQL中删除此索引会加速我的查询100x?

cwi*_*ick 10 mysql indexing

我有以下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从表中检索标题.

这是怎么回事?

编辑:有关我正在做的更多信息:

  • 查询缓存已禁用
  • 运行OPTIMIZE TABLE和ANALYZE TABLE无效
  • 6,620,372行的'is_active'设置为True.874,714行的'is_active'设置为False.
  • 使用FORCE INDEX(is_active)再次加快查询速度.
  • MySQL版本5.1.54

tho*_*ter 7

看起来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做出糟糕的决定.