我有这张桌子:
CREATE TABLE `search_engine_rankings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`keyword_id` int(11) DEFAULT NULL,
`search_engine_id` int(11) DEFAULT NULL,
`total_results` int(11) DEFAULT NULL,
`rank` int(11) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
`indexed_at` date DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_ranking` (`keyword_id`,`search_engine_id`,`rank`,`indexed_at`),
KEY `search_engine_rankings_search_engine_id_fk` (`search_engine_id`),
CONSTRAINT `search_engine_rankings_keyword_id_fk` FOREIGN KEY (`keyword_id`) REFERENCES `keywords` (`id`) ON DELETE CASCADE,
CONSTRAINT `search_engine_rankings_search_engine_id_fk` FOREIGN KEY (`search_engine_id`) REFERENCES `search_engines` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=244454637 DEFAULT CHARSET=utf8
它在生产中有大约250M行。
当我这样做时:
select id,
rank
from search_engine_rankings
where keyword_id = 19000
and search_engine_id = 11
and indexed_at = "2010-12-03";
......它运行得非常快。
当我添加url列(VARCHAR)时:
select id,
rank,
url
from search_engine_rankings
where keyword_id = 19
and search_engine_id = 11
and indexed_at = "2010-12-03";
......它运行得很慢。
有什么想法吗?
答案 0 :(得分:1)
单独的索引可以满足第一个查询 - 无需读取基表来获取Select子句中的值。第二个语句需要读取基表,因为URL列不是索引的一部分。
UNIQUE KEY `unique_ranking` (`keyword_id`,`search_engine_id`,`rank`,`indexed_at`),
基本表中的行与索引中的行的物理顺序不同,因此基表的读取可能涉及相当大的磁盘抖动。
您可以将其视为优化的一种证明 - 在第一个查询中,磁盘抖动是避免,因为引擎足够聪明,可以查询索引中所请求的值选择条款;它已经将该索引读入RAM中的where子句,因此它利用了这一事实。
答案 1 :(得分:1)
除了蒂姆的回答。 Mysql中的索引只能从左到右使用。这意味着它可以在WHERE子句中使用索引的列,直到您使用它们为止。
目前,您的UNIQUE索引为keyword_id
,search_engine_id
,rank
,indexed_at
。这将能够过滤keyword_id
和search_engine_id
列,仍然需要扫描其余行以过滤indexed_at
但如果您将其更改为:keyword_id
,search_engine_id
,indexed_at
,rank
(只是订单)。这样就可以过滤列keyword_id
,search_engine_id
和indexed_at
我相信它能够完全使用该索引来读取表格的相应部分。
答案 2 :(得分:0)
我知道这是一个很老的帖子,但我遇到了同样的情况,我没有找到答案。 这确实发生在MySQL中,当你有varchar列时,它需要花费大量的时间处理。我的查询花了大约20秒处理1.7M行,现在大约是1.9秒。
好的,首先,从这个查询中创建一个视图:
CREATE VIEW view_one AS
select id,rank
from search_engine_rankings
where keyword_id = 19000
and search_engine_id = 11
and indexed_at = "2010-12-03";
其次,相同的查询,但内部联接:
select v.*, s.url
from view_one AS v
inner join search_engine_rankings s ON s.id=v.id;
答案 3 :(得分:0)
TLDR:我通过在表上运行optimize
解决了这个问题。
我刚才也经历过。即使在主键上查找并仅选择一些行也很慢。经过测试,我发现它不仅限于varchar列,选择一个int也花费了大量时间。
大致像这样的查询大约需要3秒钟:
select someint from mytable where id in (1234, 12345, 123456)
。
虽然查询大致像这样花费了<10毫秒:
select count(*) from mytable where id in (1234, 12345, 123456)
。
这里批准的答案是也要建立一个跨越someint的索引,这样做会很快,因为mysql可以从索引中获取它需要的所有信息,而不必触摸表。这可能在某些设置中有效,但是我认为这是一个愚蠢的解决方法-显然是错误的,从表中获取三行不需要三秒钟!此外,大多数应用程序只是执行“从mytable中选择*”,并且在应用程序端进行更改并不总是那么简单。
optimize table
之后,两个查询都需要10ms。