我继承了维护一个通过SQL代理作业每晚执行的存储过程。它已经运行了好几个月,但是所有突然的昨晚都重复了一些工作而错过了一些工作。
作业在半夜运行,此时没有用户。我从有问题的运行之前恢复了数据库的备份到测试服务器,重新运行了程序,一切正常。这也是一个小数据,可能每晚100-200行。
以下是遇到问题的过程中的一个循环的表示:
DECLARE @uniqueId int
DECLARE @examId int
DECLARE @TempSingleContactTable TABLE
(
uniqueId int IDENTITY(1,1) PRIMARY KEY,
examId int not null,
contactEmail nvarchar(max) null,
)
[data inserted into @TempSingleContactTable]
WHILE EXISTS (SELECT * FROM @TempSingleContactTable)
BEGIN
Select top 1 @uniqueId = uniqueId,
@examId = examID, from @TempSingleContactTable
[*****PROBLEM HERE- this line with same value for @examId ran multiple times, but eventually continued]
DELETE FROM @TempSingleContactTable WHERE examID = @examId
END
我能看到的唯一可能导致上述问题的是DELETE调用无效。对表变量的DELETE调用是否可能不是瞬时的?
修改 任何可能导致@TempSingleContactTable中的删除偶尔失败的信息都非常感激。
编辑2: 另外的调查显示,这种自动化的每晚一次的程序在两个月内两次以同样的方式失败。有趣的是,每次失败,前一天晚上的运行并没有改变任何数据,它总是应该。遗憾的是,没有记录信息来确定可能导致之前夜晚问题的原因。它们似乎必须相关,尽管它可能是一个红鲱鱼。我已经添加了日志记录,希望能够找到实际的根本原因。
答案 0 :(得分:3)
从你看来你已经继承了一个穷人的光标"。不知怎的,人们听说游标是“邪恶的”。然后他们想出这个=( 我不会开始讨论如何基于集合优先于基于游标(读取:逐行)操作。在某些情况下,你别无选择;也许这也是一个。
将你的循环转换为一个不错的游标可能已经“稳定”了。循环的那一部分;但它也立即显示出一点“问题”'你的循环。
乍一看,等效光标将是:
DECLARE @uniqueId int
DECLARE @examId int
DECLARE @TempSingleContactTable TABLE
(
uniqueId int IDENTITY(1,1) PRIMARY KEY,
examId int not null,
contactEmail nvarchar(max) null
)
-- [data inserted into @TempSingleContactTable]
DECLARE exams_loop CURSOR LOCAL FAST_FORWARD
FOR SELECT uniqueId, examID
FROM @TempSingleContactTable
OPEN exams_loop
FETCH NEXT FROM exams_loop INTO @uniqueId, @examId
WHILE @@FETCH_STATUS = 0
BEGIN
-- internals...
FETCH NEXT FROM exams_loop INTO @uniqueId, @examId
END
CLOSE exams_loop
DEALLOCATE exams_loop
但是当仔细观察时会有一个问题:循环结束会删除给定examID
的所有记录。因此,如果有多个记录具有相同的examID
,则表示将跳过某些uniqueID
值。 (评论:它甚至不确定哪些,从来没有想过依靠它们处于自然顺序,因为场上有PK!)
因此,以下代码是更好的替代品:
DECLARE exams_loop CURSOR LOCAL FAST_FORWARD
FOR SELECT MIN(uniqueId), examID
FROM @TempSingleContactTable
GROUP BY examID
OPEN exams_loop
FETCH NEXT FROM exams_loop INTO @uniqueId, @examId
WHILE @@FETCH_STATUS = 0
BEGIN
-- internals...
FETCH NEXT FROM exams_loop INTO @uniqueId, @examId
END
CLOSE exams_loop
DEALLOCATE exams_loop
这次它确实是胜出而不是随机的uniqueID
,但公平地说,我认为可重复性(这就是我们所说的)在这里真的是优先于随机性。
无论如何,总结:
=>
DECLARE @TempSingleContactTable TABLE
(
uniqueId int IDENTITY(1,1) PRIMARY KEY,
examId int not null UNIQUE (examId, uniqueId),
contactEmail nvarchar(max) null
)
这样,当你删除时,你至少会在该字段上有一个索引。 (尽管我非常不鼓励在@ table-variables上进行密集操作,但是当你在那里放入大量数据时,他们往往会向南走,更不用说开始对它进行操作...... #temp-tables在这方面更加强大!)