当前位置: 首页 > 知识库问答 >
问题:

TSQL“滞后”分析功能-出乎意料的糟糕性能

卢普松
2023-03-14

在以前的工作中,我们必须比较项目x和项目x-1以获得大量数据(~10亿行)。由于这是在SQL Server2008R2上完成的,我们必须使用自联接。很慢。

我想我要试验一下滞后函数;如果速度快,这将是非常有价值的。我发现它快了2到3倍,但由于它应该是一个简单的操作,而且它的查询计划/表扫描更简单/大大减少了,所以我非常失望。下面复制的代码。

创建数据库:

IF EXISTS (SELECT name 
           FROM sys.databases 
           WHERE name = N'TestDBForLag')
   DROP DATABASE TestDBForLag
GO

create database TestDBForLag
ALTER DATABASE TestDBForLag SET RECOVERY SIMPLE 
go

use TestDBForLag
go

set nocount on

create table big (g binary(16) not null)
go

begin transaction

declare @c int = 0

while @c < 100
begin
    insert into big(g) values(cast(newid() as binary(16)))
    set @c += 1
end
commit

go 10000 -- n repeats of last batch, "big" now has 1,000,000 rows

alter table big
    add constraint clustered_PK primary key clustered (g)
set statistics time on
set statistics io on

-- new style
select  
    g, lag(g, 1) over (order by g) as xx
from big
order by g

-- old style
select  obig.g, 
(
    select max(g)
    from big as ibig
    where ibig.g < obig.g
) as xx
from big as obig
order by g
(1000000 row(s) affected)
Table 'Worktable'. {edit: everything zero here}.

**Table 'big'. Scan count 1, logical reads 3109**, {edit: everything else is zero here}.

SQL Server Execution Times: CPU time = 1045 ms,  elapsed time = 3516 ms.

---

(1000000 row(s) affected)

**Table 'big'. Scan count 1000001, logical reads 3190609**, {edit: everything else is zero here}.

SQL Server Execution Times:CPU time = 2683 ms,  elapsed time = 3439 ms.
select @@version 

返回

Microsoft SQL Server 2014 - 12.0.4213.0 (X64) Developer Edition (64-bit) 
on Windows NT 6.1 <X64> (Build 7601: Service Pack 1)

编辑:

根据@vnov的评论,在我发帖之前,我确实仔细地打折了客户端开销。我看的是CPU时间,而不是总体时间。测试:

select *
from big

Table 'big'. Scan count 1, logical reads 3109, {rest zero}
SQL Server Execution Times: CPU time = 125 ms,  elapsed time = 2840 ms.

select count(*)
from big

Table 'big'. Scan count 1, logical reads 3109, {rest zero}
SQL Server Execution Times: CPU time = 109 ms,  elapsed time = 129 ms.
select 
    sum(cast(g as tinyint)) as sumg
from (
    select g
    from big
) as xx


select 
    sum(cast(g as tinyint)) as sumg, 
    sum(cast(glag as tinyint)) as sumglag
from (
    select g, lag(g, 1) over (order by g) as glag
    from big
) as xx
---------- nonlagging batch --------------
use TestDBForLag
set nocount on

DECLARE crsr CURSOR FAST_FORWARD READ_ONLY FOR 
select g from big order by g 

DECLARE @g binary(16), @sumg int = 0
OPEN crsr

FETCH NEXT FROM crsr INTO @g
WHILE (@@fetch_status <> -1)
BEGIN
    IF (@@fetch_status <> -2)
    BEGIN
        set @sumg += cast(@g as tinyint)
    END
    FETCH NEXT FROM crsr INTO @g
END

CLOSE crsr
DEALLOCATE crsr

select @sumg as sumg

go 300


---------- lagging batch --------------
use TestDBForLag
set nocount on

DECLARE crsr CURSOR FAST_FORWARD READ_ONLY FOR 
select g from big order by g

DECLARE @g binary(16), @sumg int = 0 
DECLARE @glag binary(16) = 0, @sumglag int = 0
OPEN crsr

FETCH NEXT FROM crsr INTO @g
WHILE (@@fetch_status <> -1)
BEGIN
    IF (@@fetch_status <> -2)
    BEGIN
        set @sumg += cast(@g as tinyint)
        set @sumglag += cast(@glag as tinyint)  -- the only ...
        set @glag = @g  -- ... differences
    END
    FETCH NEXT FROM crsr INTO @g
END

CLOSE crsr
DEALLOCATE crsr

select @sumg as sumg, @sumglag as sumglag

go 300
-- trace save duration as microseconds, 
-- divide by 1000 to get back to milli
select 
    cast(textdata as varchar(8000)) as textdata, 
    avg(duration/1000) as avg_duration_ms
from trace
group by cast(textdata as varchar(8000))

对我来说,非滞后光标平均需要13.65秒,光标模拟滞后需要16.04秒。后者的大部分额外时间将来自解释器处理额外语句的开销(我预计如果用C实现会少得多),但无论如何,计算延迟的额外时间都不到20%。

那么,这种解释听起来合理吗?有人能提出为什么lag在select语句中表现如此糟糕吗?

共有1个答案

鄂育
2023-03-14

检查这两个变体的执行计划,您就会看到发生了什么。为此,我使用免费版本的SQL Sentry Plan Explorer。

我比较了这三个查询(再加上一个outer apply):

select count(*)
from big;

-- new style
select  
    g, lag(g) over (order by g) as xx
from big
order by g;

-- old style
select  obig.g, 
(
    select max(g)
    from big as ibig
    where ibig.g < obig.g
) as xx
from big as obig
order by g;

1)lag是使用窗口假脱机实现的,它提供的行数是临时工作表的两倍(1,999,999)(在本例中它在内存中,但仍然在内存中)。窗口假脱机不会缓存工作表中的所有1,000,000行,它只缓存窗口大小。

2)旧风格查询的计划非常好。优化器很聪明,它扫描表一次,并对每行执行top索引搜索,以计算max。是的,它有百万次搜索,但所有内容都在内存中,所以它相对较快。

3)悬停在计划操作符之间的粗箭头上,你会看到实际的数据大小。它是窗口假脱机的两倍。所以,当一切都在内存和CPU限制下时,它就变得很重要了。

4)旧样式的查询可以重写为:

select  obig.g, A.g
from big as obig
OUTER APPLY
(
    SELECT TOP(1) ibig.g
    FROM big as ibig
    WHERE ibig.g < obig.g
    ORDER BY ibig.g DESC
) AS A
order by obig.g;
 类似资料:
  • 问题内容: 我在此DataBricks帖子中看到,SparkSql支持窗口函数,特别是我正在尝试使用lag()窗口函数。 我有几行信用卡交易,并且已经对它们进行了排序,现在我要遍历各行,并为每一行显示交易金额,以及当前行金额与上一行金额的差额。 在DataBricks帖子之后,我提出了这个查询,但是它给我抛出了一个异常,我无法理解为什么。 这是在PySpark中。tx是我在注册为临时表时创建的数据

  • 我看到一个简单的存储循环的性能出乎意料的差,它有两个存储:一个是16字节的前进步幅,另一个总是在同一个位置1,如下所示: 在程序集中,此循环3可能如下所示: 当访问的内存区域在L2中时,我希望它在每次迭代中运行少于3个周期。第二个商店只是不断击中相同的位置,应该增加大约一个循环。第一个存储意味着从L2引入一行,因此每4次迭代也会逐出一行。我不确定如何评估L2成本,但即使保守地估计L1每个周期只能执

  • 问题内容: 因此,我希望它不会被编译,并且不会: 但这确实是: 是什么赋予了?它也不应该编译吗? 另外,由于运算符,这个问题很难找到。 问题答案: Java将工作解释为1加2。请参见Unary运算符部分。

  • 我正在测试简单拓扑以检查Kafka喷口的性能。它包含kafka spout和Bolt来确认每个元组。Bolt执行方法: 拓扑结构如下所示: 其他拓扑设置: 我在10分钟内得到了1.5kk元组。螺栓的性能约为0,5。所以我的逻辑很简单:如果我双倍喷注和螺栓并行提示-我将得到双倍的性能。下一个测试是1个工人2个Kafka喷口,2个简单的夹子螺栓和Topology.Acker.Executors=2。以

  • 在我第一次使用JavaFX时,场景被错误地显示,我没有找到原因。例如,在E(fx)clipse页面的第一个基本教程中提出了以下代码: 它应该显示文本“Hello FX”,但显示以下内容: 我的Java版本是适用于Windows 64(Win 7)的8u65。

  • 译者注:该小结关于错误处理的观点,译者并不完全赞同,关于本小结的部分想法请参考关于16.10.2小节错误处理的一些见解 依附于第13章模式的描述和第17.1小节与第17.2.4小节的总结。 16.10.1 不要使用布尔值: 像下面代码一样,创建一个布尔型变量用于测试错误条件是多余的: var good bool // 测试一个错误,`good`被赋为`true`或者`false`