英文:
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;
这将仍然返回结果集 2
和 3
,尽管两个语句都在延迟批处理中执行错误。
实际上,如果我们将这些语句移出延迟批处理,然后仍然会得到 1
和 2
:
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;
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论