有人可以解释为什么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):
如果我在查询末尾添加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 Join
(here是xml):
因此SQL Server知道输出是有序的,但是当我使用窗口函数时,它会做额外的排序。为什么?
here很好地解释了Table Spool
的行为。
答案 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
执行计划(注意排序已结束):