存储过程在第一步失败后仍在运行。

huangapple go评论63阅读模式
英文:

Stored procedure running despite failing at the first step

问题

我正在解决一个存储过程的问题,该存储过程应该将数据从一个 SQL Server 实例发送到托管在 Azure 上的另一个 SQL Server 实例。

在调查后,我发现存储过程的工作方式是首先根据参数确定要传输哪些表。因此,我最终得到了一个名为 @transferTable 的表变量,它看起来像这样:

id     tableName
===================
23     tableName1
55     tableName23
72     tableName111

然后,在一个循环中,设置如下:

SELECT @recordID = MIN(id) FROM @transferTable

WHILE @recordID IS NOT NULL
BEGIN
-- 更新尝试
    UPDATE transferlog
    SET LastAttempt = GetDate()
    WHERE id = @recordID;
-- 尝试
   SET @SQLCommand 'Insert into Azure.dbo.table blah blah' -- 此部分目前存在问题
   print 'about to run sql command' --这个会打印
   Execute (@SQL Command)
   print 'ran sql' --这个不会打印
-- 更新成功
   UPDATE transferlog
   SET LastUpdate = GetDate()
   WHERE id=@recordID;
SELECT @recordID = MIN(id) FROM @transferTable WHERE id > @recordID
END

这是存储过程的工作原理草图。目前它在第一个表上失败,我知道原因 - Azure 版本的表上有一个 NOT NULL 约束,而它正在尝试插入的记录中有一个 NULL 值。这对我来说是有道理的。

但让我困惑的是,传输日志表不仅更新了第一个表中有问题的 NULL 列的 LastAttempt 列,还更新了后续的所有表。

为什么会发生这种情况?存储过程的作者期望LastUpdate 只在没有错误发生时运行,但似乎情况并非如此。我们如何确保LastUpdate列仅在插入到 Azure SQL Server 数据库时没有发生错误时才更新?

英文:

I am troubleshooting a stored procedure that is supposed to send data from a SQL Server instance to another SQL Server instance hosted on Azure.

The way the stored procedure works after investigating it is it first figures out based on parameter what tables it is going to transfer. So I end up with at able variable @transferTable that looks like

id     tableName
===================
23     tableName1
55     tableName23
72     tableName111

Then in a loop, that look is setup like

SELECT @recordID = MIN(id) FROM @transferTable

WHILE @recordID IS NOT NULL
BEGIN
-- Update attempt
    UPDATE transferlog
    SET LastAttempt = GetDate()
    WHERE id = @recordID;
-- attempt
   SET @SQLCommand 'Insert into Azure.dbo.table blah blah' -- this part is broken right now
   print 'about to run sql command' --this prints
   Execute (@SQL Command)
   print 'ran sql' --this does not print
-- Update success
   UPDATE transferlog
   SET LastUpdate = GetDate()
   WHERE id=@recordID;
SELECT @recordID = MIN(id) FROM @transferTable WHERE id > @recordID
END

This is the sketch of how this stored procedure works. Right now it is failing on the first table and I know why - there is a NOT NULL constraint on the Azure version of the table and one of the records that it is trying to insert has a NULL value in it. That makes sense enoguh to me.

What does not make sense is the transfer log table is updating both the LastAttempt and LastUpdate columns for the first table which has the problematic NULL value in a column, but also for all the tables after it.

Why is this occurring? The expected behavior by the author of the stored procedure though that the LastUpdate would run if and only if no error occurred but that does not seem to be the case. How can we ensure that LastUpdated column is actually only updated if and only if no errors occurred when INSERT'ing into the Azure SQL Server database?

答案1

得分: 3

以下是翻译好的部分:

不是所有 SQL Server 中的错误都是 "相等" 的。有些错误不会导致批处理中止,使用 EXEC (@SQL) 或(更好的)sys.sp_executesql 来执行延迟批处理中的错误不会导致外部批处理结束(这通常也是期望的行为),更不用说有一些错误是在批处理解析时发生的“预批处理”中止。

以以下批处理为例:

SELECT 1;
EXEC sys.sp_executesql N'SELECT 1 / 0;';
SELECT 2;
EXEC sys.sp_executesql N'SELECT * FROM dbo.DoesNotExist;';
SELECT 3;

这将仍然返回结果集 23,尽管两个语句都在延迟批处理中执行错误。

实际上,如果我们将这些语句移出延迟批处理,然后仍然会得到 12

SELECT 1;
SELECT 1 / 0;
SELECT 2;
SELECT * FROM dbo.DoesNotExist;
SELECT 3;

但需要注意的是,3 不会返回,因为引用不存在的对象会在相同范围内导致批处理中止错误。

如果您想确保批处理始终中止,那么一种方法是启用 XACT_ABORT

SET XACT_ABORT ON;
SELECT 1;
EXEC sys.sp_executesql N'SELECT 1 / 0;';
SELECT 2;
EXEC sys.sp_executesql N'SELECT * FROM dbo.DoesNotExist;';
SELECT 3;

这只会返回 1。您还可以确保在失败时启动和提交事务以便 "自动" 回滚任何内容。

或者,您可以使用 BEGIN TRY... CATCH,但是我不太看到这里的好处:

BEGIN TRY
    SELECT 1;
    EXEC sys.sp_executesql N'SELECT 1 / 0;';
    SELECT 2;
    EXEC sys.sp_executesql N'SELECT * FROM dbo.DoesNotExist;';
    SELECT 3;
END TRY
BEGIN CATCH
    THROW;
END CATCH;
英文:

Not all errors in SQL Server are "equal". Some will not cause a batch to abort, and an error in a deferred batch, by using EXEC (@SQL) or (better) sys.sp_executesql, won't cause the outer batch to end (that is actually often desired behaviour as well), not to mention there are some that are "pre-batch" aborting as they occur at the time the batch is parsed.

Take the following batch:

SELECT 1;
EXEC sys.sp_executesql N'SELECT 1 / 0;';
SELECT 2;
EXEC sys.sp_executesql N'SELECT * FROM dbo.DoesNotExist;';
SELECT 3;

This will still result in the result sets with 2 and 3 being returned, despite that both statements executed in a deferred batch error.

In fact, if we move those statements out of deferred batches, then you still get 1 and 2:

SELECT 1;
SELECT 1 / 0;
SELECT 2;
SELECT * FROM dbo.DoesNotExist;
SELECT 3;

Note, however, that 3 isn't returned, as referencing an object that doesn't exist does result in a batch aborting error in the same scope.

If you want to ensure that the batch is always aborted, then one method is to enable XACT_ABORT:

SET XACT_ABORT ON;
SELECT 1;
EXEC sys.sp_executesql N'SELECT 1 / 0;';
SELECT 2;
EXEC sys.sp_executesql N'SELECT * FROM dbo.DoesNotExist;';
SELECT 3;

This just results in 1 being returned. You may also want to ensure that you BEGIN and COMMIT a TRANSACTION so that on a failure anything is "automagically" rolled back.

Alternatively, you could use a BEGIN TRY... CATCH, however, I don't really see the benefit here:

BEGIN TRY
    SELECT 1;
    EXEC sys.sp_executesql N'SELECT 1 / 0;';
    SELECT 2;
    EXEC sys.sp_executesql N'SELECT * FROM dbo.DoesNotExist;';
    SELECT 3;
END TRY
BEGIN CATCH
    THROW;
END CATCH;

huangapple
  • 本文由 发表于 2023年2月14日 00:00:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/75438318.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定