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

Kub*_*tek 3 sql t-sql sql-server sql-server-2008

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

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

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

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

create table Jednostki (JednostkaID nchar(5),
primary key (JednostkaID))
Run Code Online (Sandbox Code Playgroud)

StanyJednostek包含29187行,而IndeksID此表中有1676个不同的值).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
Run Code Online (Sandbox Code Playgroud)

这是执行计划:

在此输入图像描述

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

select
    StanyJednostek.JednostkaID, StanyJednostek.IndeksID
from StanyJednostek
    inner join
        (select distinct IndeksID from StanyJednostek) as Zmiany
        on StanyJednostek.IndeksID = Zmiany.IndeksID 
Run Code Online (Sandbox Code Playgroud)

请注意,查询优化器也停止提示上创造更多的指数IndeksIDStanyJednostek.

在此输入图像描述

HASH任一连接上使用提示导致以下执行计划:

在此输入图像描述

Cod*_*ent 7

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

StanyJednostek
INNER JOIN Jednostki 
INNER JOIN (SELECT DISTINCT IndeksID FROM StanyJednostek)
Run Code Online (Sandbox Code Playgroud)

第一次加入几乎没有什么可写回家 - 29187到94行不是问题.但是查询优化器猜测来自此连接的结果集是错误的.它认为这个临时结果集只有1行.

因此,它选择一个嵌套循环并认为它只扫描StanyJednostek一个(估计执行次数= 1).实际上,它将扫描StanyJednostek20,802次(第一个结果集中的行,请参阅执行次数).

请注意,DISTINCT操作员无处可寻.它在两个连接执行后应用.当然,到那时你正在处理607,147,974行.

由于它IndeksID是复合主键的一部分(而不是最左侧的键),因此SQL Server不会单独保留详细的统计信息.因此指数建议.

编辑:

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

  2. 为什么SQL Server提升DISTINCT运营商?根据它的猜测,它看到DISTINCT操作员将在加入之前或之后应用于20,802行 - 没有区别!所以我猜他只选了一个.

一些优化建议:

  1. SELECT DISTINCT IndeksID不需要子查询,在所有!这可能会带来最大的性能提升.

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

  3. 您可以通过添加OPTION (FORCE ORDER)到查询的末尾来强制连接顺序.但要小心谨慎地使用它.

  4. 您可以通过强制加入INNER HASH JOIN,但同样要注意不会立即可见的不需要的效果.任何类型的查询提示都存在风险.