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 在这方面要强大得多!)