我有以下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
'is_active'列标记了我想在大多数但不是全部查询中忽略的行。我有一些查询定期从这个表中读取块。其中一个看起来像这样:
SELECT id,title from track where (track.is_active=1 and track.id > 5580702) ORDER BY id ASC LIMIT 10;
此查询需要一分钟才能执行。这是执行计划:
> 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 |
+----+-------------+-------+------+----------------+--------+---------+-------+---------+-------------+
现在,如果我告诉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 |
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
现在,真正奇怪的是,如果我强迫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 |
+----+-------------+-------+-------+---------------+---------+---------+------+---------+-------------+
我只是不明白这种行为。在'is_active'索引中,行应按is_active排序,后跟id。我在查询中使用了'is_active'和'id'列,所以看起来它应该只需要在树周围做一些跳来找到ID,然后使用这些ID从表中检索标题。
发生了什么事?
编辑:关于我正在做什么的更多信息:
答案 0 :(得分:7)
看起来MySQL对如何使用索引做出了糟糕的决定。
从该查询计划中,它显示它可以使用PRIMARY或is_active索引,并且它已选择is_active以便首先通过track.is_active缩小。但是,它仅使用索引的第一列(track.is_active)。这得到3747543结果,然后必须进行过滤和排序。
如果它选择了PRIMARY索引,它将能够使用索引缩小到1597518行,并且它们将按照track.id的顺序检索,这应该不需要进一步排序。那会更快。
新信息:
在使用FORCE INDEX的第三种情况下,MySQL正在使用is_active索引,但现在不是仅使用第一列,而是使用两列(请参阅key_len)。因此,它现在能够通过is_active进行缩小,并使用相同的索引对id进行排序和过滤,并且由于is_active是单个常量,因此第二列满足ORDER BY(即索引的单个分支中的行已经是按排序顺序)。这似乎比使用PRIMARY更好 - 甚至可能是你想要的,对吗?
我不知道为什么没有FORCE INDEX就不使用这个索引的两列,除非查询在两者之间以微妙的方式发生了变化。如果不是,我会把它归结为MySQL做出错误的决定。
答案 1 :(得分:1)
我认为加速是由你的where子句引起的。我假设它只检索整个大表中的一小部分行。对小型子集上的is_active执行检索数据的表扫描比通过大型索引文件进行过滤更快。遍历单个列索引要比遍历组合索引快得多。
答案 2 :(得分:0)
你可以尝试几件事: