子查询或 leftjoin with group by 哪个更快?
Posted
技术标签:
【中文标题】子查询或 leftjoin with group by 哪个更快?【英文标题】:subquery or leftjoin with group by which one is faster? 【发布时间】:2011-11-13 12:35:39 【问题描述】:我必须在我的应用程序中使用总计列显示运行总计...所以我使用以下查询来查找运行总计...我发现两者都按我的需要工作。在一个中,我使用了左联接和 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
【问题讨论】:
请用 SQL 供应商名称标记您的问题。对于 Oracle、SQL-server、mysql 等,答案会有所不同。 我已经为 MS sql server 2005 做了这个 您提供的 2 个答案没有给出相同的结果。将 t2.indItzik Ben Gan 的this document 是关于在 SQL Server 中计算运行总数的一个很好的资源,该资源作为他的活动的一部分提交给 SQL Server 团队,以进一步扩展其最初的 SQL Server 2005 实施中的OVER
子句.在其中他展示了一旦进入数万行游标如何执行基于集合的解决方案。 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 |
+-------------------------------+-----------+------------+
我测试了以下案例(链接显示执行计划)
-
Left Join and Group By
相关子查询2000 row plan,10000 row plan
CTE from Mikael's (updated) answer
CTE below
包含额外的 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,对于 10k 行,通常为 50,005,000 或更多 (n² + n) / 2
,这显然呈指数增长。
在 2,000 行的情况下,GROUP BY
和子查询版本之间的主要区别在于前者在连接之后具有流聚合,因此有三列馈入其中(T1.ind
、T2.col1
、@987654353 @) 和 T1.ind
的 GROUP BY
属性,而后者被计算为标量聚合,在连接之前使用流聚合,只有 T2.col1
馈入其中并且根本没有设置 GROUP BY
属性。可以看出,这种更简单的安排在减少 CPU 时间方面具有明显的好处。
对于 10,000 行的情况,子查询计划存在额外差异。它添加了一个eager spool,它将所有ind,cast(col1 as bigint)
值复制到tempdb
。在快照隔离的情况下,它比聚集索引结构更紧凑,最终效果是减少了大约 25% 的读取次数(因为基表为版本信息保留了相当多的空白空间),当此选项关闭时,它会变得不那么紧凑(可能是由于bigint
与int
的差异)和更多的读取结果。这减少了子查询和按版本分组之间的差距,但子查询仍然获胜。
然而,明显的赢家是递归 CTE。对于“无间隙”版本,从基表读取的逻辑现在是 2 x (n + 1)
,反映了 n
索引搜索 2 级索引以检索所有行加上末尾的附加行,该行不返回任何内容并终止递归.然而,这仍然意味着要处理 22 页表需要 20,002 次读取!
递归 CTE 版本的逻辑工作表读取非常高。每个源行似乎可以读取 6 个工作表。这些来自存储前一行输出的索引假脱机,然后在下一次迭代中再次读取(Umachandar Jayachandran here 对此进行了很好的解释)。尽管数量众多,但它仍然是表现最好的。
【讨论】:
【参考方案2】:我想你会发现递归 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
任何其他更快的方法
是的,有。如果您正在寻找出色的性能,您应该在一个简单的选择中提取您的数据,并在您进行演示时在客户端上进行运行总计计算。
【讨论】:
@Eriksson 不错的一位先生,但我从数据库的角度询问出色的性能。谢谢先生。 您需要将连接条件切换为C.ind+1 = t.ind
以使递归部分可分割。
我也刚刚想到,这假设id
序列中没有间隙。我的答案有一个适用于空白的版本。
@Martin - 我知道。您不太可能希望对整个表执行此操作(无 where 子句)并且标识是完整的,除非您在每次运行时都这样做。该顺序也很可能与身份顺序不同。如果您绝对需要在服务器上进行计算,您可以使用带有新主键 int 列的临时表,并用您需要求和的行填充临时表。然后就可以使用 CTE 版本了。另一方面,有了那个临时表,你就可以进行古怪的更新了。
@Mikael - 我的答案中处理差距的版本仅比具有平等寻求的版本效率略低。它仍然寻找索引的正确部分并返回前 1 行。可以肯定的是,尽管对于大量行,游标会比我目前介绍的所有游标更有效。【参考方案3】:
你的问题不是很精确,所以这里有一些应该回答的一般规则。
添加索引。在您过于简化的示例中,它将位于 col1。 使用EXPLAIN
比较查询。这将为您提供有关较大数据会发生什么的提示。
测试(真实)数据并优化您的服务器。查询时间将取决于许多参数。例如,您的数据是否适合服务器的内存?还是您的缓冲区配置足够大?
使用缓存来转移来自数据库服务器的查询。Memcached 是最常用的内存应用级缓存,但其他缓存存在于每个级别。
【讨论】:
以上是关于子查询或 leftjoin with group by 哪个更快?的主要内容,如果未能解决你的问题,请参考以下文章