使用FAST_FORWARD游标循环的存储过程快速启动,结束缓慢

时间:2010-10-12 14:59:23

标签: sql tsql sql-server-2008 cursor query-optimization

我有一个存储过程,它使用FAST_FORWARD游标按时间顺序循环一组~300k记录,并根据许多运行变量和标志的状态将它们分配给声明集,部分实现为表变量。关于如何做这个基于集合的我已经考虑了很多,我不能这样做。所以我坚持使用光标方法,需要优化此代码。

我注意到前10%的进度加载和处理非常快(2000行/秒),接近20%的进度已经减慢到大约300行/秒,最后它已经放慢到大约60行/秒。

IMO这可能是由于以下四个原因:

  • 光标变慢,我认为不太可能使用FAST_FORWARD光标
  • 处理速度变慢。对于我的“组”,我正在使用表插入,更新和删除的表变量。在任何给定的时刻都有最大值。这些变量大约有10行。
  • 插入目标表的速度变慢。我不明白为什么会这样,我没有为它们定义触发器,它们只是普通的表。
  • 邪恶的魔法

或者我的百分比计数器坏了:

SET @curprogress = @curprogress + 1
IF (@curprogress - ((@totprogress / 100) * (FLOOR(@curprogress * 100 / @totprogress)))) BETWEEN 0 AND 1 BEGIN
    SET @msg = CAST(FLOOR(@curprogress * 100 / @totprogress) AS VARCHAR)
    RAISERROR('%s%s', 0, 1, @msg, '%...') WITH NOWAIT;
END

有没有人知道要查找什么以及如何加快查询速度?

我的代码的符号表示:

WHILE....
-- Fetch new record to be assigned to one of the open declaration sets
FETCH NEXT INTO @row_field1, @row_field2....
IF (@flag2 = 1) AND ((@flag1 = 0) OR (@row_field1 <> @prevrow_field1)) 
BEGIN
    -- Logging info: we are closing a child declaration set
    INSERT INTO @logtable SELECT '--> LOG MESSAGE'
    INSERT INTO @logtable
    SELECT format_message(@row_field1, @calc_field2, field3...)
    FROM @runningtable_sub S LEFT JOIN @runningtable_main M ON S.MainID = M.ID

    -- Update enddate of parent
    UPDATE M SET M.enddate = DATEADD(day,365,S.enddate)
    FROM @runningtable_sub S
    LEFT JOIN @runningtable_main M
    ON S.MainID = M.ID

    -- close and save child
    INSERT INTO outputtable_main
    SELECT @field1, COALESCE(Z.Field1,'NULL'), S.startdate, S.enddate,
        M.Startdate, M.Enddate
    FROM @runningtable_sub S 
    LEFT JOIN @runningtable_main M ON S.MainID = M.ID

    -- delete child from running table
    DELETE FROM @runningtable_sub WHERE S.enddate < @curdate
END

1 个答案:

答案 0 :(得分:0)

我可以想到很多原因这种情况正在放缓,但如果不知道数据的基数,就很难缩小范围。

随机观察:

  1. 你的format_message()函数肯定是一只狗;所有UDF都是。但是你每次传递多少行?
  2. @runningtable_main永远不会被清除。
  3. 更新费用很高
  4. 删除很贵。如果您使用临时表并重新编写实现,则可以截断而不是删除
  5. 要自己解决一些问题,请添加检测:

    DECLARE @now DATETIME, @duration INT, @rowcount INT
    
    WHILE....
    -- Fetch new record to be assigned to one of the open declaration sets
    FETCH NEXT INTO @row_field1, @row_field2....
    IF (@flag2 = 1) AND ((@flag1 = 0) OR (@row_field1 <> @prevrow_field1)) 
    BEGIN
        PRINT '---------------'
    
        -- Logging info: we are closing a child declaration set
        INSERT INTO @logtable SELECT '--> LOG MESSAGE'
        SET @now = GETDATE()
        INSERT INTO @logtable
        SELECT format_message(@row_field1, @calc_field2, field3...)
        FROM @runningtable_sub S LEFT JOIN @runningtable_main M ON S.MainID = M.ID
        SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
        RAISERROR('%i row(s) inserted into @logtable, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT
    
        -- Update enddate of parent
        SET @now = GETDATE()
        UPDATE M SET M.enddate = DATEADD(day,365,S.enddate)
        FROM @runningtable_sub S
        LEFT JOIN @runningtable_main M
        ON S.MainID = M.ID
        SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
        RAISERROR('%i row(s) updated in @runningtable_main, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT
    
        -- close and save child
        SET @now = GETDATE()
        INSERT INTO outputtable_main
        SELECT @field1, COALESCE(Z.Field1,'NULL'), S.startdate, S.enddate,
            M.Startdate, M.Enddate
        FROM @runningtable_sub S 
        LEFT JOIN @runningtable_main M ON S.MainID = M.ID
        SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
        RAISERROR('%i row(s) inserted into outputtable_main, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT
    
        -- delete child from running table
        SET @now = GETDATE()
        DELETE FROM @runningtable_sub WHERE S.enddate < @curdate
        SELECT @rowcount = @@ROWCOUNT, @duration = DATEDIFF(ms,@now,GETDATE)
        RAISERROR('%i row(s) deleted from @runningtable_sub, %i milliseconds',-1,-1,@rowcount,@duration) WITH NOWAIT
    END
    

    如果你包含了你的整个代码(包括游标声明),而不仅仅是一个符号摘录,那么这里的某个人可能会为你做一些更高效的工作和/或一起避免游标。