具有 FAST_FORWARD 游标循环的存储过程启动快,结束慢
我有一个存储过程,它使用 FAST_FORWARD 游标按时间顺序循环一组约 300k 的记录,并根据大量运行变量和标志的状态将它们分配给声明集,部分实现为表变量。我已经考虑了很多关于如何做这个基于集合的事情,但我就是做不到。所以我坚持使用光标方法,并且需要优化这段代码。
我注意到,前 10% 的进度加载和处理速度非常快(2000 行/秒),接近 20% 的进度已减慢至约 300 行/秒,最后减慢至约 60 行/秒。
IMO 这可能是由于 4 个原因造成的:
- 游标速度减慢,我认为使用 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
I have a stored procedure that uses a FAST_FORWARD cursor to chronologically loop over a set of ~300k records and assigns them to declaration sets based on the state of a lot of running variables and flags, partly implemented as table variables. I have given a lot of thought on how to do this set-based and I just can't do it. So I am stuck with the cursor approach, and need to optimize this code.
What I have noticed is that the first 10% of progress is loaded and processed very quickly (2000 rows/sec), near 20% progress it has slowed to about 300 rows/sec and in the end it has slowed down to about 60 rows/sec.
IMO this can be due to 4 reasons:
- The cursor slows down, which I would think unlikely with a FAST_FORWARD cursor
- The processing slows down. For my "groups" I am using table variables in which I am inserting, updating and deleting. At any given moment there are max. about 10 rows in those variables.
- The inserting into target tables slows down. I don't see why this would be, I have no triggers defined on them and they are just ordinary tables.
- Evil magic
Either that or my percentage counter is broken:
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
Has anybody any clue what to look for and how to go on speeding up this query?
Symbolic exerpt of my code:
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我可以想到这种速度减慢的很多原因,但在不知道数据基数的情况下很难缩小范围。
随机观察:
要自己弄清楚其中一些内容,请添加检测:
如果您包含整个代码(包括游标声明),而不仅仅是符号摘录,这里有人也许可以重新设计它,以提高效率和/或避免光标。
I could think of lots of reasons this is slowing down, but it is hard to narrow it down without knowing the cardinality of your data.
Random observations:
To figure some of it out for yourself, add instrumentation:
If you included your entire code (including the cursor declaration), rather than just a symbolic excerpt, someone here could probably rework it for you to be much more efficient and/or avoid cursors all together.