查看此查询。这非常简单(请参阅文章末尾的表和索引定义以及重现脚本):
SELECT MAX(Revision)
FROM dbo.TheOneders
WHERE Id = 1 AND 1 = (SELECT 1);
注意:“AND 1 = (SELECT 1) 只是为了防止此查询被自动参数化,我觉得这混淆了这个问题——不管有没有那个子句,它实际上得到了相同的计划
这是计划(粘贴计划链接):
由于那里有一个“top 1”,我很惊讶地看到了流聚合运算符。这对我来说似乎没有必要,因为保证只有一排。
为了检验这个理论,我尝试了这个逻辑上等效的查询:
SELECT MAX(Revision)
FROM dbo.TheOneders
WHERE Id = 1
GROUP BY Id;
这是那个计划(粘贴计划链接):
果然,group by plan不用stream aggregate operator也能过得去。
请注意,这两个查询都从索引的末尾“向后”读取并执行“top 1”以获得最大修订。
我在这里错过了什么? 流聚合是否真的在第一个查询中起作用,或者它是否应该能够被消除(这只是优化器的一个限制,它不是)?
顺便说一下,我意识到这不是一个非常实际的问题(两个查询都报告 0 毫秒的 CPU 和运行时间),我只是对这里展示的内部结构/行为感到好奇。
这是我在运行上面两个查询之前运行的设置代码:
DROP TABLE IF EXISTS dbo.TheOneders;
GO
CREATE TABLE dbo.TheOneders
(
Id INT NOT NULL,
Revision SMALLINT NOT NULL,
Something NVARCHAR(23),
CONSTRAINT PK_TheOneders PRIMARY KEY NONCLUSTERED (Id, Revision)
);
GO
INSERT INTO dbo.TheOneders
(Id, Revision, Something)
SELECT DISTINCT TOP 1000
1, m.message_id, 'Do...'
FROM sys.messages m
ORDER BY m.message_id
OPTION (MAXDOP 1);
INSERT INTO dbo.TheOneders
(Id, Revision, Something)
SELECT DISTINCT TOP 100
2, m.message_id, 'Do that thing you do...'
FROM sys.messages m
ORDER BY m.message_id
OPTION (MAXDOP 1);
GO
WHERE
如果没有行匹配该子句,您可以看到此聚合的作用。在那种情况下,零行进入聚合,但它仍然发出一个,因为
NULL
在这种情况下要返回正确的语义。这是一个标量聚合,而不是向量聚合。
您的“逻辑上等效”查询不等效。添加
GROUP BY Id
将使它成为矢量聚合,然后正确的行为是不返回任何行。有关更多信息,请参阅Fun with Scalar and Vector Aggregates。