为什么选择嵌套循环会导致 "self join" 的执行时间过长
Why are nested loops chosen causing long execution time for "self join"
免责声明:这不是如何提高性能的问题,而是首先为什么不好的问题。
下面的查询实际上是一些更大查询的本质,但小到足以说明我不明白的问题。
所涉及的 table 是(跳过那些 - 我希望 - 不相关的列):
create table StanyJednostek (JednostkaID nchar(5), IndeksID nchar(18),
primary key (JednostkaID, IndeksID))
create table Jednostki (JednostkaID nchar(5),
primary key (JednostkaID))
StanyJednostek
包含 29187 行,而此 table 中有 1676 个不同的 IndeksID
值。 Jednostki
包含 94 行。
现在,此查询需要两分钟多的时间才能完成:
select
StanyJednostek.JednostkaID, StanyJednostek.IndeksID
from StanyJednostek
inner join
(select distinct IndeksID from StanyJednostek) as Zmiany
on StanyJednostek.IndeksID = Zmiany.IndeksID
inner join
Jednostki on StanyJednostek.JednostkaID = Jednostki.JednostkaID
执行计划如下:
令我困扰的是如此庞大的实际行数:607147974。这显然需要两分钟才能完成。虽然我知道这个数字是从哪里来的(这是 29187 乘以 20802,而 20802 是 StanyJednostek
和 Jednostki
之间的成功连接数),但我不太明白为什么查询优化器决定选择嵌套循环这里?为什么 Zmiany
不是某种迭代的临时集而不是整个源 table?同样有趣的是,虽然查询的最后两行似乎无关紧要,但如果我删除这些行,执行计划更改和嵌套循环将替换为散列:
select
StanyJednostek.JednostkaID, StanyJednostek.IndeksID
from StanyJednostek
inner join
(select distinct IndeksID from StanyJednostek) as Zmiany
on StanyJednostek.IndeksID = Zmiany.IndeksID
请注意,查询优化器也停止建议在 StanyJednostek
中的 IndeksID
上创建附加索引。
在 either join 上使用 HASH
提示会导致以下执行计划:
第二个内部联接扩展了行数,因为 StanyJednostek.JednostkaID = Jednostki.JednostkaID
是 N:1。这会将散列连接所需的内存增加到系统可用以上,因此无法使用散列连接。
至于为什么缺失索引提示没有了:因为hash join不需要它。很可能缺少索引会提高性能。
SQL 服务器将联接重新排序为它认为最有效的顺序。在这种情况下,它猜错了。从您的第一个执行计划中注意到,连接顺序如下:
StanyJednostek
INNER JOIN Jednostki
INNER JOIN (SELECT DISTINCT IndeksID FROM StanyJednostek)
第一个连接几乎没有什么值得大书特书的 - 29187 到 94 行不是问题。但是查询优化器对这个连接的结果集的猜测是错误的。它认为这个临时结果集只有 1 行。
因此,它选择了一个嵌套循环并认为它只会扫描 StanyJednostek
个(估计执行次数 = 1)。实际上,它会扫描 StanyJednostek
20,802 次(第一个结果集中的行数,请参阅 执行次数 )。
请注意,DISTINCT
运算符还没有找到。它在两个连接都已执行后应用。当然,到那时你正在处理 607,147,974 行。
由于 IndeksID
是复合主键的一部分(也不是最左边的键),SQL 服务器不会单独保留详细的统计信息。因此索引建议。
编辑:
是否由于一些过时的统计数据导致了这个错误的猜测?不太可能。第一个连接匹配 JednostkaID
。查看该列如何出现在两个 table 的 PK 中。 SQL服务器可能认为因为在PK中,所以一定是唯一的。这可能是查询优化器中的错误。
为什么 SQL 服务器提升了 DISTINCT
运算符? 从它的猜测中,它看到 DISTINCT
运算符将应用于连接之前或之后的 20,802 行 - 没有区别!所以我的猜测是它只选择了一个。
一些优化建议:
根本不需要 SELECT DISTINCT IndeksID
子查询!这可能对性能带来最大的提升。
如果你真的因为某些不在这个问题中的原因坚持保留 SELECT DISTINCT
,我建议将它具体化为临时 table。它强制 SQL 服务器将 DISTINCT
应用于较小的一组行 (29,187)
您可以通过在查询末尾添加 OPTION (FORCE ORDER)
来强制执行连接顺序。但是要小心谨慎地使用它。
您可以通过 INNER HASH JOIN
强制加入一个 has join,但同样要注意不会立即可见的不良影响。任何类型的查询提示都有风险。
免责声明:这不是如何提高性能的问题,而是首先为什么不好的问题。
下面的查询实际上是一些更大查询的本质,但小到足以说明我不明白的问题。
所涉及的 table 是(跳过那些 - 我希望 - 不相关的列):
create table StanyJednostek (JednostkaID nchar(5), IndeksID nchar(18),
primary key (JednostkaID, IndeksID))
create table Jednostki (JednostkaID nchar(5),
primary key (JednostkaID))
StanyJednostek
包含 29187 行,而此 table 中有 1676 个不同的 IndeksID
值。 Jednostki
包含 94 行。
现在,此查询需要两分钟多的时间才能完成:
select
StanyJednostek.JednostkaID, StanyJednostek.IndeksID
from StanyJednostek
inner join
(select distinct IndeksID from StanyJednostek) as Zmiany
on StanyJednostek.IndeksID = Zmiany.IndeksID
inner join
Jednostki on StanyJednostek.JednostkaID = Jednostki.JednostkaID
执行计划如下:
令我困扰的是如此庞大的实际行数:607147974。这显然需要两分钟才能完成。虽然我知道这个数字是从哪里来的(这是 29187 乘以 20802,而 20802 是 StanyJednostek
和 Jednostki
之间的成功连接数),但我不太明白为什么查询优化器决定选择嵌套循环这里?为什么 Zmiany
不是某种迭代的临时集而不是整个源 table?同样有趣的是,虽然查询的最后两行似乎无关紧要,但如果我删除这些行,执行计划更改和嵌套循环将替换为散列:
select
StanyJednostek.JednostkaID, StanyJednostek.IndeksID
from StanyJednostek
inner join
(select distinct IndeksID from StanyJednostek) as Zmiany
on StanyJednostek.IndeksID = Zmiany.IndeksID
请注意,查询优化器也停止建议在 StanyJednostek
中的 IndeksID
上创建附加索引。
在 either join 上使用 HASH
提示会导致以下执行计划:
第二个内部联接扩展了行数,因为 StanyJednostek.JednostkaID = Jednostki.JednostkaID
是 N:1。这会将散列连接所需的内存增加到系统可用以上,因此无法使用散列连接。
至于为什么缺失索引提示没有了:因为hash join不需要它。很可能缺少索引会提高性能。
SQL 服务器将联接重新排序为它认为最有效的顺序。在这种情况下,它猜错了。从您的第一个执行计划中注意到,连接顺序如下:
StanyJednostek
INNER JOIN Jednostki
INNER JOIN (SELECT DISTINCT IndeksID FROM StanyJednostek)
第一个连接几乎没有什么值得大书特书的 - 29187 到 94 行不是问题。但是查询优化器对这个连接的结果集的猜测是错误的。它认为这个临时结果集只有 1 行。
因此,它选择了一个嵌套循环并认为它只会扫描 StanyJednostek
个(估计执行次数 = 1)。实际上,它会扫描 StanyJednostek
20,802 次(第一个结果集中的行数,请参阅 执行次数 )。
请注意,DISTINCT
运算符还没有找到。它在两个连接都已执行后应用。当然,到那时你正在处理 607,147,974 行。
由于 IndeksID
是复合主键的一部分(也不是最左边的键),SQL 服务器不会单独保留详细的统计信息。因此索引建议。
编辑:
是否由于一些过时的统计数据导致了这个错误的猜测?不太可能。第一个连接匹配
JednostkaID
。查看该列如何出现在两个 table 的 PK 中。 SQL服务器可能认为因为在PK中,所以一定是唯一的。这可能是查询优化器中的错误。为什么 SQL 服务器提升了
DISTINCT
运算符? 从它的猜测中,它看到DISTINCT
运算符将应用于连接之前或之后的 20,802 行 - 没有区别!所以我的猜测是它只选择了一个。
一些优化建议:
根本不需要
SELECT DISTINCT IndeksID
子查询!这可能对性能带来最大的提升。如果你真的因为某些不在这个问题中的原因坚持保留
SELECT DISTINCT
,我建议将它具体化为临时 table。它强制 SQL 服务器将DISTINCT
应用于较小的一组行 (29,187)您可以通过在查询末尾添加
OPTION (FORCE ORDER)
来强制执行连接顺序。但是要小心谨慎地使用它。您可以通过
INNER HASH JOIN
强制加入一个 has join,但同样要注意不会立即可见的不良影响。任何类型的查询提示都有风险。