为什么选择嵌套循环会导致“自联接”的执行时间过长

时间:2015-02-10 20:49:04

标签: sql sql-server sql-server-2008 tsql

免责声明:这不是一个如何改善效果的问题,而是为什么它首先是不好的。

以下查询实际上是一些较大查询的本质,但小到足以证明我不理解的问题。

所涉及的表是(跳过列 - 我希望 - 无关紧要):

create table StanyJednostek (JednostkaID nchar(5), IndeksID nchar(18),
primary key (JednostkaID, IndeksID))

create table Jednostki (JednostkaID nchar(5),
primary key (JednostkaID))

StanyJednostek包含29187行,而此表中有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

这是执行计划:

enter image description here

困扰我的是这么多的实际行:607147974。这显然需要两分钟才能完成。虽然我理解这个数字的来源(这是20182的29187倍,而20802是StanyJednostekJednostki之间成功连接的数量),但我不明白为什么查询优化器决定选择嵌套循环这里?为什么Zmiany不是某种迭代而不是整个源表的临时集?同样有趣的是,虽然查询的最后两行似乎无关紧要,但如果删除这些行,则执行计划更改和嵌套循环将替换为哈希:

select
    StanyJednostek.JednostkaID, StanyJednostek.IndeksID
from StanyJednostek
    inner join
        (select distinct IndeksID from StanyJednostek) as Zmiany
        on StanyJednostek.IndeksID = Zmiany.IndeksID 

请注意,查询优化工具也会停止建议在IndeksID的{​​{1}}上创建其他索引。

enter image description here

加入上使用StanyJednostek提示会导致以下执行计划:

enter image description here

2 个答案:

答案 0 :(得分:6)

SQL Server将连接重新排序为它认为最有效的连接。在这种情况下,它猜错了。从第一个执行计划中注意到,连接顺序如下:

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 Server不会单独保留详细的统计信息。因此指数建议。

修改

  1. 由于某些过时的统计信息,这是错误的猜测吗?不太可能。第一次加入与JednostkaID匹配。查看列在两个表的PK中的显示方式。 SQL Server可能认为因为它在PK中,所以它必须是唯一的。这可能是查询优化程序中的一个错误。

  2. 为什么SQL Server提升DISTINCT运算符?从猜测中可以看出,DISTINCT运算符将应用于20,802行,之前或之后加入 - 没有区别!所以我猜他只选了一个。

  3. 一些优化建议:

    1. 根本不需要SELECT DISTINCT IndeksID子查询!这可能会带来性能的最大改善。

    2. 如果你真的坚持保留SELECT DISTINCT由于某些原因不在这个问题中,我建议将其实现为临时表。它强制SQL Server在较小的行集上应用DISTINCT(29,187)

    3. 您可以通过在查询末尾添加OPTION (FORCE ORDER)来强制加入订单。但要小心谨慎地使用它。

    4. 您可以通过INNER HASH JOIN强制加入,但请再次注意不会立即显示的不良效果。任何类型的查询提示都有风险。

答案 1 :(得分:1)

第二个内部联接会扩展行数,因为StanyJednostek.JednostkaID = Jednostki.JednostkaID是N:1。这会将散列连接所需的内存增加到可用系统之上,因此无法使用散列连接。

至于为什么丢失的索引提示消失了:因为散列连接不需要它。丢失的指数很可能会改善性能。