MySql 组合(嵌套)查询和使用连接方式的查询比单独查询慢

MySql Combined (Nested) Query and Queries Using Join Way Slower than Separate Queries

问题总结

使用 MySql 5.6,我注意到组合 Select 查询(例如 select x.a from X x where x.b in (select y.b from Y y where y.c = 'something'))比使用第一个查询的结果进行两个单独的查询要慢第二个查询的 in 子句。我尝试使用 Join 语句而不是嵌套查询(受本网站其他帖子的影响)没有产生任何性能改进。

我知道这是 MySql 的一个常见问题,我在 SO 上阅读了很多关于这个问题的帖子,并尝试了一些解决方案,这些解决方案显然适用于其他发帖者,但不适用于我.

这个查询:

select ADSH_ from SECSub where Symbol_='MSFT';

速度很快并产生了这个结果:

'0001193125-10-015598'
'0001193125-10-090116'
'0001193125-10-171791'

实际上有 21 个结果,但我已将它们修剪为 3 个。

这里有一些额外的信息:

show indexes from SECSub;

产生:

explain select * from SECSub where Symbol_='MSFT';

产生:

使用第一个查询的结果查询另一个 table,如下所示:

select * from SECNum where ADSH_ in (
    '0001193125-10-015598',
    '0001193125-10-090116',
    '0001193125-10-171791);

同样快(0.094 秒)。实际查询的 in 子句使用了第一个查询的 21 个结果,但我再次将它们修剪为 3 个。

还有这个:

show indexes from SECNum;

产生:

explain select * from SECNum where ADSH_ in (
    '0001193125-10-015598',
    '0001193125-10-090116',
    '0001193125-10-171791');

产生:

但是这个组合查询:

select * 
from SECNum 
where ADSH_ in (select ADSH_ 
                from SECSub sub 
                where Symbol_='MSFT');

非常慢,需要 151 秒(相比之下,之前的查询大约需要 0.1 秒)。

explain select * from SECNum where ADSH_ in (select ADSH_ from SECSub sub where Symbol_='MSFT');

产生:

因此,在阅读了一些关于 SO 的类似帖子后,我尝试将组合查询重新转换为 Join 操作:

加入尝试 1

select * 
from SECNum num 
inner join SECSub sub on num.ADSH_ = sub.ADSH_ 
where sub.Symbol_ = 'MSFT';

此结果耗时 158 秒,甚至比使用组合查询耗时 151 秒还要慢。

explain select * from SECNum num inner join SECSub sub on num.ADSH_ = sub.ADSH_ where sub.Symbol_ = 'MSFT';

制作:

加入尝试 2

select * 
from (select sub.ADSH_ 
      from SECSub sub 
      where sub.Symbol_='MSFT') SubSelect 
join SECNum num on SubSelect.ADSH_ = num.ADSH_;

这个结果耗时 151 秒,与我的组合查询相同..

explain select * from (select sub.ADSH_ from SECSub sub where sub.Symbol_='MSFT') SubSelect join SECNum num on SubSelect.ADSH_ = num.ADSH_;

制作:

很明显,我(还)不知道自己在做什么。关于如何编写与我的组合查询或任何这些 Join 查询产生相同结果的查询的任何建议,这些查询的运行速度与我有两个单独查询的情况(大约 0.1 秒)一样快?

让我从这个查询开始:

select * 
from SECNum 
where ADSH_ in (select ADSH_ 
                from SECSub sub 
                where Symbol_ = 'MSFT');

这方面的最佳索引是综合索引SECSub(Symbol_, ADSH_)。我猜是因为这个索引不可用,所以 MySQL 似乎做出了错误的选择。它正在执行完整的 table 扫描并检查 where 条件,而不是使用索引来查找适当的行。覆盖索引(包含两列)应该将 MySQL 优化器放在正确的路径上。

有时,in 带有子查询的优化不是很好(虽然我认为这是在 5.6 中修复的)。也可以尝试使用 not exists:

进行查询
select * 
from SECNum sn
where not exists (select ADSH_ 
                  from SECSub sub 
                  where sub.Symbol_ = 'MSFT' AND
                        sub.ADSH_ = sn.ADSH_
                 );

首先,我尝试了@Gordon Linoff 的建议(或隐含的建议)在 SECSub 上添加一个由 Symbol_ 和 ADSH_ 组成的复合索引。这对我尝试的任何查询的性能没有影响。

在努力解决这个性能问题时,我注意到 SECNum.ADSC_ 被定义为 character set latin1SECSub.ADSC_ 被定义为 character set utf8_general_ci

然后我怀疑当我通过复制和粘贴第一个查询的输出创建第二个查询时:

select * from SECNum where ADSH_ in (
    '0001193125-10-015598',
    '0001193125-10-090116',
    '0001193125-10-171791');

in 子句中的文字字符串使用了 character set latin1,因为它们都是从 MySQL Workbench 中输入(嗯,复制和粘贴)的这或许可以解释为什么这个查询如此之快。

完成后:

alter table SECSub convert to character set latin1;

组合查询(子查询)速度很快(不到 1 秒),explain 首次显示查询正在使用索引。使用 Join.

的变体也是如此

我想如果我在最初的问题中包含实际的 table 定义,有人会向我指出分配给 table 列的字符集不一致索引和查询。学过的知识。下次我 post 时,我将包括 table 定义(至少对于那些参与索引和查询的列,我正在询问)。

IN ( SELECT ... )没有优化好。事实上,在 5.6 之前它优化非常很差。 5.6 添加了一种有用的技术。但一般最好把它变成一个JOIN,即使是5.6。

FROM ( SELECT ... ) a
JOIN ( SELECT ... ) b ON ...

在 5.6 之前,执行 非常 很差,因为两个子查询都没有索引,因此对其中一个 tmp table 进行了大量 table 扫描。 5.6(或者是 5.7?)'discovers' 子查询的最佳索引,因此帮助很大。

FROM tbl
JOIN ( SELECT ... ) x ON ...

将始终(至少在 5.6 之前)首先执行子查询,进入临时 table。然后它将执行 NLJ(嵌套循环连接)。因此,对于 ON 子句中的任何列,您都应该在 tbl 中有一个索引。如果有多个列,则将其设为复合索引。

复合查询通常优于单列查询。请记住 MySQL 几乎从不在单个 SELECT 中使用两个索引。 ("Index merge")

每当询问性能问题时,请提供 SHOW CREATE TABLE

根据这些原则,您应该能够编写出性能更好的查询,而无需进行太多试验。