sql - 为什么选择嵌套循环会导致 "self join"的执行时间过长

标签 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。这显然需要两分钟才能完成。虽然我知道这个数字来自哪里(这是 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 

请注意,查询优化器也不再建议在 IndeksID 上创建额外的索引。在 StanyJednostek .

enter image description here

使用 HASH对任一连接的提示都会导致以下执行计划:

enter image description here

最佳答案

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

编辑:
  • 这是由于一些过时的统计数据导致的错误猜测吗? 不见得。第一个连接匹配于 JednostkaID .查看该列如何出现在两个表的 PK 中。 SQL Server 可能会认为,因为它在 PKs 中,所以它必须是唯一的。这可能是查询优化器中的一个错误。
  • 为什么 SQL Server 会吊起 DISTINCT运算符(operator)? 从它的猜测中,它看到DISTINCT运算符将应用于 20,802 行,在连接之前或之后 - 没有区别!所以我的猜测是它只选择一个。

  • 一些优化建议:
  • SELECT DISTINCT IndeksID根本不需要子查询!这可能会带来最大的性能提升。
  • 如果你真的坚持保留SELECT DISTINCT由于某些原因不在此问题中,我建议将其具体化为临时表。它强制 SQL Server 应用 DISTINCT在较小的一组行上 (29,187)
  • 您可以通过添加 OPTION (FORCE ORDER) 来强制加入顺序到查询结束。但是要小心谨慎地使用它。
  • 您可以通过 INNER HASH JOIN 强制已加入,但同样,请注意无法立即看到的不良影响。任何类型的查询提示都有风险。
  • 关于sql - 为什么选择嵌套循环会导致 "self join"的执行时间过长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28441468/

    相关文章:

    sql - 我可以在同一个查询结果(oracle)中返回多个计数器吗?

    mysql - Rails 3.2.2 SQL 查询生成器

    mysql更新列内部连接另一个表

    SQL Server 日期列不大于另一个日期列

    Sql Merge 锁定整个目标表

    sql - SQL中的递归选择

    sql - 将表与行连接起来

    sql - 在 WHERE 子句中使用字段组合

    c# - MS SQL nvarchar id 字段包含空格

    sql - 在SQL查询中选择多行到一个变量中