【问题标题】:SQL Server - why is scanning done twice for the same table?SQL Server - 为什么对同一个表进行两次扫描?
【发布时间】:2015-01-19 11:35:13
【问题描述】:

有谁知道为什么sql server 选择查询表'building'两次?有什么解释吗?可以只用一个 table seek 来完成吗?

这是代码示例:

DECLARE @id1stBuild INT = 1
    ,@number1stBuild INT = 2
    ,@idLastBuild INT = 5
    ,@numberLastBuild INT = 1;
DECLARE @nr TABLE (nr INT);

INSERT @nr
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10);

CREATE TABLE building (
    id INT PRIMARY KEY identity(1, 1)
    ,number INT NOT NULL
    ,idStreet INT NOT NULL
    ,surface INT NOT NULL
    )

INSERT INTO building (number,idStreet,surface)
SELECT bl.b
    ,n.nr
    ,abs(convert(BIGINT, convert(VARBINARY, NEWID()))) % 500
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY n1.nr) b
    FROM @nr n1
    CROSS JOIN @nr n2
    CROSS JOIN @nr n3
    ) bl
CROSS JOIN @nr n

--***** execution plan for the select below
SELECT *
FROM building b
WHERE b.id = @id1stBuild
    AND b.number = @number1stBuild
    OR b.id = @idLastBuild
    AND b.number = @numberLastBuild

DROP TABLE building

对此的执行计划总是相同的:两个 Clustered Index Seek 通过 Merge Join (Concatenation) 统一起来。其余的不太重要。 这是执行计划:

【问题讨论】:

  • 您的 where 子句缺少括号。我认为这可能是造成这种情况的原因...(b.id=@id1stBuild AND b.number=@number1stBuild) OR (b.id=@idLastBuild AND b.number=@numberLastBuild) 因为OR
  • 感谢您的快速回复。我试了一下,还是一样的执行计划。

标签: sql sql-server sql-execution-plan


【解决方案1】:

它没有扫描两次。它正在寻找两次。

您的查询在语义上与以下内容相同。

SELECT *
FROM   building b
WHERE  b.id = @id1stBuild
       AND b.number = @number1stBuild
UNION
SELECT *
FROM   building b
WHERE  b.id = @idLastBuild
       AND b.number = @numberLastBuild 

执行计划执行两次查找并合并结果。

【讨论】:

  • 我从没想过将OR 运算符视为UNION,但它实际上很有意义。 +1
  • UNION 不是UNION ALL,这非常很重要。想想如果@id1stBuild 等于@idLastBuild 并且@number1stBuild 等于@numberLastBuild 的结果是什么。 OR 查询返回一行,而 UNION ALL 则返回两行。
  • 感谢所有回复。我是执行计划的新手,想更多地研究这个主题。我不明白sql是两次还是只遍历一次表索引?如果表的行数超过 10.000.000 行,会发生什么情况。这将在 Web 应用程序中使用,我希望尽快运行。
  • @Emarian - 表越大,执行两次搜索而不是扫描整个事物的优势就越大。
【解决方案2】:

为什么要对同一张表进行两次扫描?

不是扫描,是搜索,这一切都不同。

将 OR 实现为 UNION,然后通过 MERGE JOIN 实现 UNION。被称为'merge union'

合并联合

现在让我们稍微改变一下查询:

select a from T where b = 1 or c = 3

  |--Stream Aggregate(GROUP BY:([T].[a]))
   |--Merge Join(Concatenation)
        |--Index Seek(OBJECT:([T].[Tb]), SEEK:([T].[b]=(1)) ORDERED FORWARD)
        |--Index Seek(OBJECT:([T].[Tc]), SEEK:([T].[c]=(3)) ORDERED FORWARD)

我们现在有一个合并连接(连接)和一个流聚合,而不是连接和排序不同的运算符。发生了什么?合并连接(连接)或“合并联合”根本不是真正的连接。它是由与合并连接相同的迭代器实现的,但它实际上执行了一个联合,同时保留了输入行的顺序。最后,我们使用流聚合来消除重复。 (有关使用流聚合消除重复的更多信息,请参阅这篇文章。)此计划通常是更好的选择,因为不同的排序使用内存,并且如果在流时内存不足,可能会将数据溢出到磁盘聚合不使用内存。

【讨论】:

    【解决方案3】:

    您可以尝试以下方法,它只提供一次搜索和轻微的性能提升。正如@Martin_Smith 所说,您编写的代码相当于Union

    SELECT *
    FROM building b
    WHERE b.id IN (@id1stBuild , @idLastBuild) 
        AND 
            (
                (b.id = @id1stBuild AND b.number = @number1stBuild) OR 
                (b.id = @idLastBuild AND b.number = @numberLastBuild)
            )
    

    【讨论】:

    • 它提供了一个单次搜索迭代器,但仍然执行了两次搜索。 When is a Seek not a Seek?
    • @MartinSmith 在这种情况下,查询计划建议对 IN 子句中的参数进行排序,然后 SEEK 谓词使用范围扫描,其中 id > start 和 id
    • 它使用合并间隔来消除重复。搜索执行两次。看实际执行计划中的“执行次数”。
    • @MartinSmith 确实,我看到你之前已经为这个主题做出了一些贡献,例如dba.stackexchange.com/questions/14789/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-14
    • 2011-04-24
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-12
    相关资源
    最近更新 更多