为什么SQL Server在合并联接后添加排序

时间:2018-12-18 07:39:32

标签: sql-server tsql

有人可以解释为什么SQL Server在Sort之后添加Merge Join,该结果返回已经返回的按Sort运算符中的列排序的输出。

这里是要重现的查询。 初始数据:

DROP TABLE Temp1;
CREATE TABLE Temp1 (
    Id1 INT,
    Id2 INT,
    Value1 INT
)
GO

DROP TABLE Temp2;
CREATE TABLE Temp2 (
    Id1 INT,
    Id2 INT,
    Value2 INT
)
GO

DECLARE @I AS INT;
SET @I = 0

WHILE @I < 10000
BEGIN
    INSERT INTO Temp1 (Id1, Id2, Value1) VALUES (@I, @I + 1, @I)
    INSERT INTO Temp2 (Id1, Id2, Value2) VALUES (@I, @I + 1, @I)

    SET @I = @I + 1
END
GO

CREATE UNIQUE CLUSTERED INDEX PK_Temp1 ON Temp1 (Id1, Id2)
GO

CREATE UNIQUE CLUSTERED INDEX PK_Temp2 ON Temp2 (Id1, Id2)
GO

和实际查询:

SELECT
    t1.Id1,
    t1.Id2,
    t1.Value1,
    MAX(t1.Value1) OVER (PARTITION BY t1.Id1) AS MaxValue1,
    t2.Value2
FROM Temp1 t1
LEFT JOIN Temp2 t2 ON t1.Id1 = t2.Id1 AND t1.Id2 = t2.Id2

最可疑的行为是Sort之后的Merge Join

Here是完整的计划(包括XML):

Plan with sort oprator

如果我在查询末尾添加ORDER BY而不添加MAX

SELECT
    t1.Id1,
    t1.Id2,
    t1.Value1,
    t2.Value2
FROM Temp1 t1
LEFT JOIN Temp2 t2 ON t1.Id1 = t2.Id1 AND t1.Id2 = t2.Id2
ORDER BY t1.Id1

然后计划仍然是最佳方案,仅使用Merge Joinhere是xml): enter image description here

因此SQL Server知道输出是有序的,但是当我使用窗口函数时,它会做额外的排序。为什么?

here很好地解释了Table Spool的行为。

4 个答案:

答案 0 :(得分:1)

在COMPATIBILITY_LEVEL小于或等于110(SQL Server 2012(11.x))的数据库中,查询无需SORT即可运行。可能是在improvement of SQL Estimator期间添加了一些新的错误。如果可能,您可以更改兼容性级别。

COMPATIBILITY_LEVEL = 110的查询的plan

答案 1 :(得分:0)

好吧,还需要对分区进行排序以找到每个分区的最大值。如果您要像这样更改代码,则在执行计划中将看不到额外的排序。

SELECT
    t1.Id1,
    t1.Id2,
    t1.Value1,
    MAX(t1.Value1) OVER (PARTITION BY t1.Id1 ORDER BY (SELECT 1)) AS MaxValue1,
    t2.Value2
FROM Temp1 t1
LEFT JOIN Temp2 t2 ON t1.Id1 = t2.Id1 AND t1.Id2 = t2.Id2

答案 2 :(得分:0)

很好的问题,简短的答案是我希望这是一个错误。我能够在SQL Server 2017(开发人员版)RTM-CU12,版本14.0.3045.24(启用了跟踪标志4199)上重现此内容。
我认为您应该将此repro发布到https://feedback.azure.com/forums/908035-sql-server(但将其标记为错误的优化,而不是错误)-MS倾向于将“错误”读取为“错误的结果”,因此将其称为错误会带来风险他们关闭它)

更长的答案和一些背景。此计划中使用的多对多合并联接只能“完全”维护其中一个输入的顺序(我认为这是较低的输入,但尚未完成以100%确定性进行证明的工作)。来自其他输入的数据存储在工作表中,如果主表具有重复值,则可以重复使用。如果一个表的数据为1,A / 1,B,而另一个表的数据为1,C / 1,D,则SO;结果将是1,A,C / 1,A,D / 1,B,C / 1,B,D。因此第二个的排序顺序不能完全保留。
但是,出于此查询的目的,没有人关心完整保留完整的排序顺序,我们只关心Id列。而且,多对多合并联接中的倒带逻辑仅适用于具有相同ID值的行。我想我们可以称其为部分订单保留:通过ID(或在您的示例中,Id1,Id2)对订单的保证得以维持,但是在那些列中具有相同值的行可以互换。因此,正如您所断言的那样,合并联接的输出绝对仍然可以保证以Id1,Id2的顺序排列。
(如果您想获得更多背景知识,这里是我如何理解合并加入工作的完整说明:https://sqlserverfast.com/epr/merge-join/

我的第一个理论是,由于上述原因,优化器认为只有一个表保留了其排序顺序。事实证明这是不正确的。如果将OVER规范中的ORDER BY更改为使用t2.Id,则会得到完全相同的多余排序。

我又做了两个实验:ORDER BY t2.Id1,t2.Id2;和ORDER BY t1.ID1,t1.Id2。这就是它变得有趣的地方。最后一个(但只有最后一个)为我提供了一个执行计划,而没有额外的Sort运算符。

所以我现在的理论是:

  • 如果合并联接后所需的顺序是输入的排序顺序的完全匹配,并且保留了完整的顺序,则不添加排序。
  • 如果合并联接后所需的顺序是输入的排序顺序的精确匹配,并且保留了部分顺序,那么即使实际上不是必需的,也将添加排序
  • 如果合并联接后所需的顺序与任何输入的排序顺序完全匹配,则添加排序-即使所需的排序顺序是现有输入的子集排序顺序,因此得到暗示和保证。

顺便说一句:感谢您提供完整的副本 AND 和完整的XML查询计划!那非常有用!

答案 3 :(得分:0)

SQL Server并未详尽地尝试避免所有逻辑上不必要的排序。这样做所需的逻辑在编译时会很复杂且消耗资源。

多年来,已经进行了改进,以解决大多数常见情况。 Microsoft在对Connect项目的回复中对此进行了详细说明,但遗憾的是该信息已丢失到历史记录中。

通常可以通过提供顶级ORDER BY子句来帮助排序逻辑。在您的特定情况下,合并联接保留联接键的顺序(两个键都保留),而窗口函数所需的顺序是该键的子集。我们可以通过添加与合并联接匹配的最终排序要求来避免排序:

SELECT
    t1.Id1,
    t1.Id2,
    t1.Value1,
    MAX(t1.Value1) OVER (PARTITION BY t1.Id1) AS MaxValue1,
    t2.Value2
FROM Temp1 t1
LEFT JOIN Temp2 t2 ON t1.Id1 = t2.Id1 AND t1.Id2 = t2.Id2
ORDER BY t1.Id1, t1.Id2; -- This is new

执行计划(注意排序已结束):

plan without sorts