子查询或leftjoin与哪个更快的组?

时间:2011-09-09 05:35:40

标签: sql-server-2005 subquery left-join running-total

Query Execution Plan我必须在我的应用程序中显示总计列的运行总数...所以我使用以下查询来查找运行总计..​​.我发现两者都按照我的需要工作。在一个我使用左连接与group by和另一个我使用子查询。

现在我的问题是,当我的数据每天增加数千时,哪一个更快,如果数据将限制在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

3 个答案:

答案 0 :(得分:5)

Itzik Ben Gan在SQL Server中计算运行总计的一个很好的资源是this document,它作为其竞选活动的一部分提交给SQL Server团队,以使OVER子句从最初进一步扩展SQL Server 2005实现。在其中,他展示了一旦​​你进入成千上万行游标执行基于集合的解决方案。 SQL Server 2012确实扩展了OVER子句,使这种查询更加容易。

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

正如您在SQL Server 2005上一样,但是您无法使用此功能。

Adam Machanic shows here如何使用CLR来改进标准TSQL游标的性能。

对于此表定义

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

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

所有表的聚簇索引只有1个根页。每个叶页的数量如下所示。

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

我测试了以下案例(链接显示执行计划)

  1. Left Join and Group By
  2. 相关子查询2000 row plan10000 row plan
  3. CTE from Mikael's (updated) answer
  4. CTE below
  5. 包含额外CTE选项的原因是为了提供CTE解决方案,如果ind列无法保证顺序,该解决方案仍然有用。

    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),以避免运行时出现溢出错误。另外,对于所有这些,我将结果分配给上面的变量,以便消除从考虑中发回结果所花费的时间。

    结果

    +------------------+----------+--------+------------+---------------+------------+---------------+-------+---------+
    |                  |          |        |          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)上的聚簇索引扫描驱动,并且对于返回的每一行扫描,回到T2上自我加入的表格T2.ind<=T1.ind

    这意味着重复处理相同的行。处理T1.ind=1000行时,自联接检索所有行并使用ind <= 1000求和,然后为T1.ind=1001检索相同的1000行的下一行 >并总结另外一行等等。

    2,000行表的此类操作总数为2,001,000,1万行50,005,000或更多,(n² + n) / 2明显呈指数增长。

    在2,000行的情况下,GROUP BY和子查询版本之间的主要区别在于前者在连接后具有流聚合,因此有三列供给它(T1.ind,{{ 1}},T2.col1)和T2.col1属性GROUP BY,而后者则计算为标量聚合,在联接之前使用流聚合,只有T1.ind进行进入它并且根本没有设置T2.col1属性。可以看出,这种更简单的安排在减少CPU时间方面具有可衡量的好处。

    对于10,000行的情况,子查询计划中存在其他差异。它添加了eager spool,将所有GROUP BY值复制到ind,cast(col1 as bigint)。在快照隔离开启的情况下,这比聚集索引结构更紧凑,并且净效果是将读取次数减少大约25%(因为基表为版本控制信息保留了相当多的空白空间),当此选项关闭时,它会变得不那么紧凑(可能是由于tempdb vs bigint差异)并且读取结果更多。这减少了子查询和组之间的差距,但子查询仍然获胜。

    然而,明显的赢家是递归CTE。对于“无间隙”版本,来自基表的逻辑读取现在是int,反映了2 x (n + 1)索引寻找2级索引以检索所有行加上最后一行不返回任何行并终止递归。这仍然意味着20,002次读取处理一个22页的表格!

    递归CTE版本的逻辑工作表读取非常高。似乎每个源行有6个工作表读取。它们来自存储上一行输出的索引假脱机,然后在下一次迭代中再次读取(Umachandar Jayachandran here对此的详细解释)。尽管人数众多,但仍然是表现最好的。

答案 1 :(得分:4)

我认为你会发现递归CTE的速度要快一些。

;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
  

更快的任何其他方法

是的。如果您正在寻找出色的性能,您只需在简单的选择中提取数据,并在进行演示时在客户端上执行运行总计算。

答案 2 :(得分:0)

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

  • 添加索引。在过度简化的示例中,它将位于col1上。
  • 使用EXPLAIN来比较查询。这将为您提供有关较大数据会发生什么的提示。
  • 测试(实际)数据并优化服务器。查询时间取决于许多参数。例如,您的数据是否适合服务器的内存?或者您的缓冲区配置得足够大吗?
  • 使用缓存从数据库服务器转移查询。 Memcached是最常用的内存中应用程序级缓存,但每个级别都存在其他缓存。