我有一个问题:
select SQL_NO_CACHE id from users
where id>1 and id <1000
and id in ( select owner_id from comments and content_type='Some_string');
(请注意,它缺少用于我的sphinx索引的实际大型查询,代表问题) 此查询大约需要 3.5秒(从id = 1..5000修改范围使其大约 15秒)。
users表有大约35000个条目,而comments表有大约8000个条目。
解释上述问题:
explain select SQL_NO_CACHE id from users
where id>1 and id <1000
and id in ( select distinct owner_id from d360_core_comments);
| id | select_type |表|类型| possible_keys |关键| key_len | ref |行|额外的|
| 1 |主要|用户|范围|主 |主要| 4 | NULL | 1992年|用在哪里;使用索引|
| 2 |相关的子信息| d360_core_comments |所有| NULL | NULL | NULL | NULL | 6901 |用在哪里;使用临时|
这里的单个子查询(select owner_id from d360_core_comments where content_type='Community20::Topic';
)这里差不多花了0.0秒。
但是,如果我在owner_id,content_type 上添加索引,(请注意此处的订单)
create index tmp_user on d360_core_comments (owner_id,content_type);
我的子查询在没有使用索引的情况下运行~0.0秒:
的MySQL&GT;从d360_core_comments中解释select owner_id CONTENT_TYPE = 'Community20 ::主题';
| id | select_type |表|类型| possible_keys |关键| key_len | ref |行|额外的|
<小时/> | 1 |简单| d360_core_comments |所有| NULL |空值 | NULL | NULL | 6901 |使用where |
但是现在我的主查询(select SQL_NO_CACHE id from users where id>1 and id <1000 and id in ( select owner_id from d360_core_comments where content_type='Community20::Topic');
)
现在在~0秒内运行,并按照以下说明进行操作:
的MySQL&GT;解释从id> 1和id的用户中选择SQL_NO_CACHE id &lt; 1000和id in(从d360_core_comments中选择owner_id,其中 CONTENT_TYPE = 'Community20 ::主题');
| id | select_type |表|类型| possible_keys |关键| key_len | ref |行|额外的|
<小时/> | 1 |主 |用户|范围|主要|主要| 4 | NULL | 1992年|用在哪里;使用索引|
| 2 |相关的子信息| d360_core_comments | index_subquery | tmp_user | tmp_user | 5 | func | 34 |使用where |
所以我的主要问题是:
答案 0 :(得分:3)
在没有索引的完整查询中似乎发生的事情是MySQL将构建(某种)子查询生成的所有owner_id的临时表。然后,对于与user约束匹配的users表中的每一行,将执行此临时构造中的查找。目前还不清楚开销是否正在创建临时构造,或者查询是否是次优地实现的(以便所有元素与外部查询的每一行线性匹配。
当你在owner_id上创建索引时,当你只运行子查询时,这不会改变任何东西,因为它在owner_id上没有条件,索引也没有覆盖content_type列。
但是,当您使用索引运行完整查询时,可以获得更多信息,因为我们现在具有来自外部查询的值,该值应与owner_id匹配,后者由索引覆盖。所以现在执行似乎是运行外部查询的第一部分,并且对于每个匹配的行,执行owner_id的索引查找。换句话说,可能的执行计划是:
From Index-Users-Id Get all id matching id>1 and id <1000
For Each Row
Include Row If Index-Comment-OwnerId Contains row.Id
And Row Matches content_type='Some_string'
因此,在这种情况下,运行1000(我假设)索引查找的工作比构建8000可能的owner_id的临时构造要快。但这只是一个假设,因为我不太了解MySQL。
答案 1 :(得分:2)
如果您阅读了MySQL参考手册的这一部分:Optimizing Subqueries with EXISTS
Strategy,您将看到查询优化器从以下位置转换子查询条件:
id in ( select distinct owner_id
from d360_core_comments
where content_type='Community20::Topic')
成:
exists ( select 1
from d360_core_comments
where content_type='Community20::Topic'
and owner_id = users.id )
这就是当子查询作为独立查询进行测试时,(owner_id, content_type)
上的索引无用的原因,但在考虑转换的子查询时它很有用。
答案 2 :(得分:1)
你应该知道的第一件事是MySQL无法优化依赖子查询,这是一个长期以来众所周知的MySQL缺陷,这将在MySQL 6.x中修复(只是google for“mysql dependent subquery) “你会看到的。”也就是说,子查询基本上是为users
表中的每个匹配行执行的。由于您有其他条件,因此总体执行时间取决于该条件。解决方案是用子连接替换子查询(这是您期望从MySQL中获得的优化)。
其次,子查询中存在语法错误,我认为owner_id存在一个条件。因此,当您在owner_id
上添加索引时,它会被使用,但对于第二个条件(因此没有using index
)是不够的,但是EXPLAIN
中根本没有提到的原因是问题(我认为是因为users.id
)
第三,我不知道为什么你需要那个id > 1 and id < 5000
条件,但你应该明白这些是两个范围条件,需要非常准确,有时非显而易见和数据依赖的索引方法(而不是相等)比较条件),如果你实际上不需要它们并仅用于解释为什么查询需要这么长时间,那么这是一个坏主意,它们将不会消失。
如果条件是必需的并且owner_id
上的索引仍然存在,我会按如下方式重写查询:
SELECT id
FROM (
SELECT owner_id as id
FROM comments
WHERE owner_id < 5000 AND content_type = 'some_string'
) as ids
JOIN users ON (id)
WHERE id > 1;
P.S。 (content_type, owner_id)
上的复合索引甚至可以更好地用于查询。
答案 3 :(得分:0)
第1步:使用id BETWEEN x AND y
代替id >= x AND id <= y
。您可能会发现一些令人惊讶的收益,因为它的索引更好。
第2步:调整您的子SELECT
进行过滤,这样就不必两次了:
SELECT SQL_NO_CACHE id
FROM users
WHERE id IN (SELECT owner_id
FROM comments
WHERE content_type='Some_string'
AND owner_id BETWEEN 1 AND 1000);
您的陈述中似乎有多处错误。例如,您选择2到999,可能两端都是一个,并且子选择无效。