为什么使用GROUP BY子句比不使用GROUP BY子句的集合查询显着更快?


12

我很好奇为什么有GROUP BY子句的聚合查询比没有子句的查询运行得这么快。

例如,此查询将花费近10秒钟来运行

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1

虽然这个过程不到一秒钟

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
GROUP BY CreatedDate

CreatedDate在这种情况下只有一个,因此分组查询返回的结果与未分组查询相同。

我注意到两个查询的执行计划是不同的-第二个查询使用Parallelism,而第一个查询则没有。

Query1执行计划 Query2执行计划

如果SQL Server没有GROUP BY子句,则以不同的方式评估聚合查询是否正常?在不使用GROUP BY子句的情况下,我可以做些什么来提高第一查询的性能?

编辑

我刚刚学会了可以将OPTION(querytraceon 8649)并行性的开销开销设置为0,这使查询使用某种并行性,并将运行时间减少到2秒,尽管我不知道使用此查询提示是否有任何弊端。

SELECT MIN(CreatedDate)
FROM MyTable
WHERE SomeIndexedValue = 1
OPTION(querytraceon 8649)

在此处输入图片说明

我仍然希望运行时间更短,因为查询是要在用户选择时填充一个值,因此理想情况下应该像分组查询一样是瞬时的。现在,我只是包装查询,但我知道这并不是理想的解决方案。

SELECT Min(CreatedDate)
FROM
(
    SELECT Min(CreatedDate) as CreatedDate
    FROM MyTable WITH (NOLOCK) 
    WHERE SomeIndexedValue = 1
    GROUP BY CreatedDate
) as T

编辑#2

马丁的要求提供更多信息

无论CreatedDateSomeIndexedValue对他们有独立的非唯一,非聚集索引。SomeIndexedValue实际上,它是一个varchar(7)字段,即使它存储了一个指向另一个表的PK(int)的数值。在数据库中未定义两个表之间的关系。我根本不应该更改数据库,只能写查询数据的查询。

MyTable包含超过300万条记录,并且每个记录都分配了一个属于(SomeIndexedValue)的组。组可以是1到200,000条记录中的任何

Answers:


8

看起来好像是CreatedDate按照从低到高的顺序在索引上进行索引,并进行查找以评估SomeIndexedValue = 1谓词。

当它找到第一个匹配的行时,它完成了,但是它可能会比查找到这样的行之前进行更多的查找(它假定与谓词匹配的行是根据日期随机分布的。)

看到我的答案在这里类似的问题

此查询的理想索引是on SomeIndexedValue, CreatedDate。假设您无法添加该索引,或者至少将现有索引作为一个包含的列作为SomeIndexedValue封面CreatedDate,那么您可以尝试按以下方式重写查询

SELECT MIN(DATEADD(DAY, 0, CreatedDate)) AS CreatedDate
FROM MyTable
WHERE SomeIndexedValue = 1

以防止其使用该特定计划。


2

我们可以控制MAXDOP并选择一个已知的表,例如AdventureWorks.Production.TransactionHistory吗?

当我重复使用

--#1
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT MIN(TransactionDate) 
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

费用是相同的。

顺便说一句,我希望(使之成为现实)对您的索引值进行索引搜索;否则,您可能会看到哈希匹配而不是流聚合。您可以使用非聚集索引(包括要聚合的值)来提高性能,也可以创建将聚集定义为列的索引视图。然后,您将通过索引ID命中包含聚集的聚集索引。在SQL Standard中,您可以仅创建视图并使用WITH(NOEXPAND)提示。

一个示例(我不使用MIN,因为它在索引视图中不起作用):

USE AdventureWorks ;
GO

-- Covering Index with Include
CREATE INDEX IX_CoverAndInclude
ON Production.TransactionHistory(TransactionDate) 
INCLUDE (Quantity) ;
GO

-- Indexed View
CREATE VIEW dbo.SumofQtyByTransDate
    WITH SCHEMABINDING
AS
SELECT 
      TransactionDate 
    , COUNT_BIG(*) AS NumberOfTransactions
    , SUM(Quantity) AS TotalTransactions
FROM Production.TransactionHistory
GROUP BY TransactionDate ;
GO

CREATE UNIQUE CLUSTERED INDEX SumofAllChargesIndex 
    ON dbo.SumofQtyByTransDate (TransactionDate) ;  
GO


--#1
SELECT SUM(Quantity) 
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(0))
WHERE TransactionID = 100001 
OPTION( MAXDOP 1) ;

--#2
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory 
WITH (INDEX(IX_CoverAndInclude))
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO 

--#3
SELECT SUM(Quantity)  
FROM AdventureWorks.Production.TransactionHistory
WHERE TransactionID = 100001 
GROUP BY TransactionDate
OPTION( MAXDOP 1) ;
GO

MAXDOP设置最大并行度,这限制了查询可以使用的处理器数量。基本上,这将使第二个查询的运行速度与第一个查询一样慢,因为它删除了使用并行性的功能,而这并不是我想要的。
雷切尔

@瑞秋我同意; 但是除非设置一些基本规则,否则我们无法进行任何比较。我无法轻松地将运行在64个内核上的并行进程与运行在一个内核上的单个线程进行比较。最后,我希望我们所有的机器都至少有一个逻辑CPU =-)
ooutwire 2012年

0

在我看来,该问题的原因是sql服务器优化程序不是在寻找BEST计划,而是在寻找一个好的计划,这从以下事实可以明显看出:在强制并行性之后,查询执行得更快,这是优化器具有的功能不能自己完成。

我还看到许多情况下并行化之间的区别是用不同的格式重写查询(例如,尽管大多数SQL文章建议进行参数化,但我发现它有时会导致noy并行化,即使嗅探到的参数与非参数相同也是如此) -并行化一个查询,或将两个查询与UNION ALL结合使用,有时可以消除并行化。

因此,正确的解决方案可能是尝试使用不同的编写查询的方式,例如尝试临时表,表变量,cte,派生表,参数化等,并在其中使用索引,索引视图或过滤索引。为了得到最好的计划。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.