【问题标题】:SQL Server - Delete from table variables not instantaneous?SQL Server - 从表变量中删除不是瞬时的?
【发布时间】:2015-06-08 19:33:55
【问题描述】:

我继承了维护一个通过 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: 进一步的调查表明,这种每晚一次的自动化程序在两个月内以同样的方式失败了两次。有趣的是,每次失败时,前一天晚上的运行都没有改变任何数据,而且总是如此。不幸的是,没有日志信息可以确定可能导致前几晚出现问题的原因。看起来他们必须是相关的,尽管它可能是一个红鲱鱼。我添加了日志记录,希望能找到真正的根本原因。

【问题讨论】:

  • 为什么在你的 delete 语句中使用@examId 作为你的 where 条件,而不能保证它是唯一的? @uniqueId 是更好的候选者,因为它是您的主键。
  • @Kritner,因为在该循环中完成的工作应该只针对每个考试 ID 发生一次,并且具有相同考试 ID 的单个联系人可能有多个记录。

标签: sql sql-server sql-server-2014


【解决方案1】:

看起来你继承了“穷人的光标”。不知何故,人们听说游标是“邪恶的”,然后他们想出了这个 =( 我不打算就基于集合的操作优于基于游标的操作(阅读:逐行)进行辩论。在某些情况下,您根本别无选择;也许这也是一个。

将循环转换为合适的光标可能已经“稳定”了循环的那一部分;但它也立即表明您的循环存在一些“问题”。

乍一看,等效的光标是这样的:

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

【讨论】:

  • 您能否评论一下为什么现有的 Delete (DELETE FROM @ TempSingleContactTable WHERE ExamID = @examId ) 有时似乎不起作用的根本原因是什么?
  • 说实话,我做不到。这可能是多种因素的结合,但罪魁祸首是@table 变量。这些东西可以暂时将一些数据“放在一边”存储,但对于实际的“工作”来说,它们并不是最好的工具。 (没有索引、没有统计信息、没有事务处理等)。我最好的猜测是 DELETE 语句的生成查询计划向南走,因为优化器(几乎)没有信息可以作为其决策的依据。为什么这种情况只是偶尔发生,可能是运气不好和数据古怪,但老实说,我不知道。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-02-16
  • 2013-08-07
  • 1970-01-01
  • 2011-02-08
  • 2012-03-23
  • 1970-01-01
  • 2020-09-30
相关资源
最近更新 更多