7

查询执行计划我必须在我的应用程序中使用总计列显示运行总计..​​.所以我使用以下查询来查找运行总计..​​.我发现两者都按照我的需要工作。在一个中,我使用了左联接和 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
4

3 回答 3

5

在 SQL Server 中计算运行总计的一个很好的资源是Itzik Ben Gan 的这份文档,该文档作为他的活动的一部分提交给 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在这里展示了如何使用 CLR 来提高标准 TSQL 游标的性能。

对于这个表定义

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

我在数据库中创建了包含 2,000 行和 10,000 行的表,ALLOW_SNAPSHOT_ISOLATION ON其中一个关闭了此设置(原因是我的初始结果位于数据库中,该设置导致结果令人费解)。

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

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

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

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

包含附加 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上的聚集索引扫描驱动的“三角形”嵌套循环连接,并且对于该扫描返回的每一行,寻找回表 ( ) 自连接。RunningTotalsT1T2T2.ind<=T1.ind

这意味着重复处理相同的行。处理该T1.ind=1000行时,自联接检索所有行并将其与 相加ind <= 1000,然后对于下一行,再次T1.ind=1001检索相同的 1000 行并将其与一个附加行相加,依此类推。

2,000 行表的此类操作总数为 2,001,000,对于 10k 行,通常为 50,005,000 或更多(n² + n) / 2 ,这显然呈指数增长。

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

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

然而,明显的赢家是递归 CTE。对于“无间隙”版本,来自基表的逻辑读取现在2 x (n + 1)反映了n索引搜索到 2 级索引以检索所有行以及末尾的附加行,该行不返回任何内容并终止递归。然而,这仍然意味着要处理 22 页表需要 20,002 次读取!

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

于 2011-09-17T11:58:45.717 回答
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

任何其他更快的方法

就在这里。如果您正在寻找出色的性能,您应该在一个简单的选择中提取您的数据,并在您进行演示时在客户端上进行运行总计计算。

于 2011-09-13T05:29:08.903 回答
0

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

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