SQL Server - 从表变量中删除不是即时的?

标签 sql sql-server sql-server-2014

我继承了维护一个通过 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/

相关文章:

MySQL使用where & 和条件过滤结果

php - Codeigniter 将数据库中的值放入数组中并打印出来

sql - 如何将两行值转换为一行?

c# - 许多 SQL 行合并为一行

sql-server - 无法加载文件或程序集 Microsoft.SqlServer.TransactSql

sql - 删除所有空格并将 SQL 中的多行合并为单行

android - sqlite中的SQL sqlite条件查询(Android版)

mysql - 一次性更新和选择

sql-server - 无法删除 Amazon RDS SQL Server 数据库

sql-server - 在哪里可以找到 SQL Server 2014 Service Broker 外部激活器?