我继承了维护一个通过 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: 额外的调查表明,这种每晚一次的自动化程序在两个月内以同样的方式失败了两次。有趣的是,每次失败时,前一天晚上的运行都不会改变任何数据,而且它总是应该如此。不幸的是,没有日志信息可以确定可能导致前几晚问题的原因。看起来它们必须是相关的,尽管它可能是一条红鲱鱼。我添加了日志记录,希望找到真正的根本原因。
最佳答案
从表面上看,您似乎继承了“穷人的光标”。不知何故人们听说游标是“邪恶的”然后他们想出了这个=( 我不打算就基于集合的操作优于基于游标的(阅读:逐行)操作展开辩论。在某些情况下,您别无选择;也许这也是一个。
将循环转换为合适的游标可能已经“稳定”了循环的那一部分;但它也立即表明您的循环存在一些“问题”。
乍一看,等效的游标是这样的:
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 更多在这方面很强大!)
关于SQL Server - 从表变量中删除不是即时的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30717575/