SQL 服务器 - 从 table 变量中删除不是即时的?
SQL Server - Delete from table variables not instantaneous?
我继承了通过 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 调用不起作用。对 table 变量的 DELETE 调用是否可能不是瞬时的?
编辑:
非常感谢有关可能导致从@TempSingleContactTable 删除偶尔失败的原因的任何信息。
编辑 2:
额外的调查表明,这种每晚一次的自动化程序在两个月内以同样的方式失败了两次。有趣的是,每次失败时,前一天晚上的 运行 都不会改变任何数据,而且它总是应该改变。不幸的是,没有日志信息可以确定可能导致前几晚问题的原因。看起来它们必须是相关的,尽管它可能是一条红鲱鱼。我添加了日志记录,希望找到真正的根本原因。
看起来您继承了 "poor man's cursor"。人们不知何故听说游标是 'evil' 然后他们想出了这个 =(
我不打算就基于集合的操作优于基于游标的(阅读:逐行)操作展开辩论。在某些情况下,您别无选择;也许这也是一个。
将循环转换为合适的游标可能已经 'stabilize' 循环的那一部分;但它也立即显示你的循环有一点 'problem'。
乍一看,等效的光标是这样的:
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
而不是随机的,但平心而论,我认为可重复性(这就是我们' re talking here really) 比随机性更受欢迎。
总之,总结一下:
- 宁愿使用真正的光标而不是穷人的替代品,因为它是一个糟糕的替代品
- 如果你真的想保持现在的循环,请将 table-定义更改为:
=>
DECLARE @TempSingleContactTable TABLE
(
uniqueId int IDENTITY(1,1) PRIMARY KEY,
examId int not null UNIQUE (examId, uniqueId),
contactEmail nvarchar(max) null
)
这样您在删除时至少会在该字段上有一个索引。 (尽管我极力反对对@table-变量进行密集操作,但一旦您将 'medium' 量的数据放入其中,它们往往会向南移动,更不用说开始对其进行操作了... # temp-tables 在这方面要强大得多!)
我继承了通过 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 调用不起作用。对 table 变量的 DELETE 调用是否可能不是瞬时的?
编辑: 非常感谢有关可能导致从@TempSingleContactTable 删除偶尔失败的原因的任何信息。
编辑 2: 额外的调查表明,这种每晚一次的自动化程序在两个月内以同样的方式失败了两次。有趣的是,每次失败时,前一天晚上的 运行 都不会改变任何数据,而且它总是应该改变。不幸的是,没有日志信息可以确定可能导致前几晚问题的原因。看起来它们必须是相关的,尽管它可能是一条红鲱鱼。我添加了日志记录,希望找到真正的根本原因。
看起来您继承了 "poor man's cursor"。人们不知何故听说游标是 'evil' 然后他们想出了这个 =( 我不打算就基于集合的操作优于基于游标的(阅读:逐行)操作展开辩论。在某些情况下,您别无选择;也许这也是一个。
将循环转换为合适的游标可能已经 'stabilize' 循环的那一部分;但它也立即显示你的循环有一点 'problem'。
乍一看,等效的光标是这样的:
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
而不是随机的,但平心而论,我认为可重复性(这就是我们' re talking here really) 比随机性更受欢迎。
总之,总结一下:
- 宁愿使用真正的光标而不是穷人的替代品,因为它是一个糟糕的替代品
- 如果你真的想保持现在的循环,请将 table-定义更改为:
=>
DECLARE @TempSingleContactTable TABLE
(
uniqueId int IDENTITY(1,1) PRIMARY KEY,
examId int not null UNIQUE (examId, uniqueId),
contactEmail nvarchar(max) null
)
这样您在删除时至少会在该字段上有一个索引。 (尽管我极力反对对@table-变量进行密集操作,但一旦您将 'medium' 量的数据放入其中,它们往往会向南移动,更不用说开始对其进行操作了... # temp-tables 在这方面要强大得多!)