SQL Server - 为什么同一个表扫描两次?

时间:2015-01-19 11:35:14

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

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

这是代码示例:

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

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

enter image description here

3 个答案:

答案 0 :(得分:5)

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

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

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 

执行计划执行两次搜查和结合。

答案 1 :(得分:3)

  

为什么在同一张桌子上扫描两次?

不是扫描,是寻求,而这一切都有所不同。

将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)
     

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

答案 2 :(得分:2)

您可以尝试以下操作,它只提供一次搜索并略微提升性能。正如@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)
        )