子查询还是与其中一个更快的群组左移?

查询执行计划 我必须显示在我的应用程序中的总列与总计运行总数…所以我已经使用以下查询来查找运行总数…我发现,两者都按照我的需要工作。 在一个我使用了左边的连接,并在另一个我使用子查询。

现在我的问题是,当我的数据以每天数千的增长速度哪一个更快,并且如果数据将限制在1000或2000行,那么哪一个更好…以及其他哪个更快,那么这两个? ??

declare @tmp table(ind int identity(1,1),col1 int) insert into @tmp select 2 union select 4 union select 7 union select 5 union select 8 union select 10 SELECT t1.col1,sum( t2.col1) FROM @tmp AS t1 LEFT JOIN @tmp t2 ON t1.ind>=t2.ind group by t1.ind,t1.col1 select t1.col1,(select sum(col1) from @tmp as t2 where t2.ind<=t1.ind) from @tmp as t1 

在SQL Server中计算运行总数的一个很好的资源是Itzik Ben Gan提交给SQL Server团队的这个文档 ,作为他的活动的一部分,让OVER子句从最初的SQL Server 2005实现中进一步扩展。 在这个例子中,他展示了如何进入成千上万行的游标执行基于集合的解决scheme。 SQL Server 2012确实扩展了OVER子句,使得这种查询更容易。

 SELECT col1, SUM(col1) OVER (ORDER BY ind ROWS UNBOUNDED PRECEDING) FROM @tmp 

就像你在SQL Server 2005上一样,但是这对你是不可用的。

Adam Machanic 在这里展示了如何使用CLR来改进标准TSQL游标的性能。

对于这个表格定义

 CREATE TABLE RunningTotals ( ind int identity(1,1) primary key, col1 int ) 

我在ALLOW_SNAPSHOT_ISOLATION ON的数据库中创build了包含2,000行和10,000行的表,其中一个是closures的(原因是因为我的初始结果在DB中,设置导致了结果令人费解的一面)。

所有表的聚簇索引只有1个根页面。 下面显示了每个页面的页数。

 +-------------------------------+-----------+------------+ | | 2,000 row | 10,000 row | +-------------------------------+-----------+------------+ | ALLOW_SNAPSHOT_ISOLATION OFF | 5 | 22 | | ALLOW_SNAPSHOT_ISOLATION ON | 8 | 39 | +-------------------------------+-----------+------------+ 

我testing了以下情况(链接显示执行计划)

  1. 左join和分组
  2. 相关子查询2000行计划 , 10000行计划
  3. 来自Mikael的CTE(更新)答案
  4. CTE下面

列入额外的CTE选项的原因是为了提供一个CTE解决scheme,如果ind列无法保证顺序,这个解决scheme仍然可以工作。

 SET STATISTICS IO ON; SET STATISTICS TIME ON; DECLARE @col1 int, @sumcol1 bigint; WITH RecursiveCTE AS ( SELECT TOP 1 ind, col1, CAST(col1 AS BIGINT) AS Total FROM RunningTotals ORDER BY ind UNION ALL SELECT R.ind, R.col1, R.Total FROM ( SELECT T.*, T.col1 + Total AS Total, rn = ROW_NUMBER() OVER (ORDER BY T.ind) FROM RunningTotals T JOIN RecursiveCTE R ON R.ind < T.ind ) R WHERE R.rn = 1 ) SELECT @col1 =col1, @sumcol1=Total FROM RecursiveCTE OPTION (MAXRECURSION 0); 

所有查询都添加了一个CAST(col1 AS BIGINT) ,以避免运行时发生溢出错误。 另外对于所有的人,我将结果分配给上面的variables,以消除从考虑中返回结果的时间。

结果

 +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+ | | | | Base Table | Work Table | Time | +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+ | | Snapshot | Rows | Scan count | logical reads | Scan count | logical reads | cpu | elapsed | | Group By | On | 2,000 | 2001 | 12709 | | | 1469 | 1250 | | | On | 10,000 | 10001 | 216678 | | | 30906 | 30963 | | | Off | 2,000 | 2001 | 9251 | | | 1140 | 1160 | | | Off | 10,000 | 10001 | 130089 | | | 29906 | 28306 | +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+ | Sub Query | On | 2,000 | 2001 | 12709 | | | 844 | 823 | | | On | 10,000 | 2 | 82 | 10000 | 165025 | 24672 | 24535 | | | Off | 2,000 | 2001 | 9251 | | | 766 | 999 | | | Off | 10,000 | 2 | 48 | 10000 | 165025 | 25188 | 23880 | +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+ | CTE No Gaps | On | 2,000 | 0 | 4002 | 2 | 12001 | 78 | 101 | | | On | 10,000 | 0 | 20002 | 2 | 60001 | 344 | 342 | | | Off | 2,000 | 0 | 4002 | 2 | 12001 | 62 | 253 | | | Off | 10,000 | 0 | 20002 | 2 | 60001 | 281 | 326 | +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+ | CTE Alllows Gaps | On | 2,000 | 2001 | 4009 | 2 | 12001 | 47 | 75 | | | On | 10,000 | 10001 | 20040 | 2 | 60001 | 312 | 413 | | | Off | 2,000 | 2001 | 4006 | 2 | 12001 | 94 | 90 | | | Off | 10,000 | 10001 | 20023 | 2 | 60001 | 313 | 349 | +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+ 

相关子查询和GROUP BY版本都使用由RunningTotals表( T1 )上的聚集索引扫描驱动的“三angular形”嵌套循环联接,并且对于由该扫描返回的每一行,寻找回到表( T2 )自joinT2.ind<=T1.ind

这意味着相同的行重复处理。 当处理T1.ind=1000行时,自连接检索并将所有行与ind <= 1000相加,然后对于T1.ind=1001的下一行, 再次检索相同的1000行,并与另外一行等等。

2000行表中的这种操作的总数是2,001,000,对于10k行一般为5,005,000或更多(n² + n) / 2 ,这种行为明显地呈指数增长。

在2000行的情况下, GROUP BY和子查询版本之间的主要区别在于前者在连接之后具有stream聚合,因此具有三列进入( T1.indT2.col1T2.col1 )和a T1.ind GROUP BY属性,而后者是作为一个标量聚合来计算的,在连接之前的stream聚合只有T2.col1进入它,并且根本没有设置GROUP BY属性。 可以看出,这种简单的安排在减lessCPU时间方面具有可测量的益处。

对于10,000行的情况,在子查询计划中有一个额外的差异。 它添加了一个eager spool ,将所有的ind,cast(col1 as bigint)复制到tempdb ind,cast(col1 as bigint)值中。 在快照隔离的情况下,这比聚集索引结构更紧凑,最终结果是将读取次数减less了大约25%(因为基表为版本信息保留了相当多的空白空间),当这个选项closures时,结果会变得不那么紧凑(大概是由于bigintint差异),读取结果更多。 这样可以减less子查询和组之间的差距,但是子查询依然胜出。

然而,明确的获胜者是recursionCTE。 对于“无间隙”版本来说,基表中的逻辑读取现在是2 x (n + 1)反映了n索引寻find2级索引以检索所有行加上附加的一个,而不返回任何内容并终止recursion。 这仍然意味着20,002读取处理一个22页表然而!

逻辑工作表读取的recursionCTE版本非常高。 似乎在每个源行有6个工作表读取。 这些来自存储前一行输出的索引假脱机,然后在下一次迭代中再次读取(这里由Umachandar Jayachandran对此进行了很好的解释)。 尽pipe数量很多,但这仍然是performance最好的。

我想你会发现recursionCTE更快一点。

 ;with C as ( select t.ind, t.col1, t.col1 as Total from @tmp as t where t.ind = 1 union all select t.ind, t.col1, C.Total + t.col1 as Total from @tmp as t inner join C on C.ind + 1 = t.ind ) select C.col1, C.Total from C 

任何其他方法更快

就在这里。 如果你正在寻找出色的performance,你应该简单地select你的数据,并在演示时对客户端进行总计算。

你的问题不是很精确,所以这里有一些应该回答的一般规则。

  • 添加一个索引。 在你简单的例子上,它会在col1上。
  • 使用EXPLAIN来比较查询。 这会给你提示大数据会发生什么。
  • testing(真实)数据并优化您的服务器 。 查询时间将取决于许多参数。 例如,你的数据是否适合服务器的内存? 还是你的缓冲区configuration得足够大?
  • 使用caching来转移来自数据库服务器的查询。 Memcached是最常用的内存中应用程序级caching,但其他caching存在于每个级别。