SQL Server 'Resume Next' 等效项

Posted

技术标签:

【中文标题】SQL Server \'Resume Next\' 等效项【英文标题】:SQL Server 'Resume Next' EquivalentSQL Server 'Resume Next' 等效项 【发布时间】:2009-09-11 14:02:51 【问题描述】:

我正在 VB.net 中开发一个项目,该项目采用包含 T-SQL 的大型文本文件并针对本地 SQL 数据库执行它们,但我遇到了错误处理方面的问题。

我正在使用以下技术:

VB.net 框架 3.5 SQL Express 2005

我尝试执行的 SQL 大多是直截了当的,但我的应用程序完全不知道架构或其中包含的数据。例如:

UPDATE mytable SET mycol2='data' WHERE mycol1=1
INSERT INTO mytable (mycol1, mycol2) VALUES (1,'data')
UPDATE mytable SET mycol2='data' WHERE mycol1=2
INSERT INTO mytable (mycol1, mycol2) VALUES (1,'data')
UPDATE mytable SET mycol2='data' WHERE mycol1=3

以上是我正在执行的事情的一个示例,但这些文件每个将包含大约 10,000 到 20,000 条语句。

我的问题是,当使用 sqlCommand.ExecuteNonQuery() 时,我收到了一个异常,因为第二个 INSERT 语句将命中表上的主键约束。

我需要知道发生了这个错误并记录它,还需要处理任何后续语句。我尝试将这些语句包装在 TRY/CATCH 块中,但我无法找到处理错误的方法,然后继续处理其他语句。

查询分析器似乎以这种方式运行,但在使用 sqlCommand.ExecuteNonQuery() 时不会。

那么是否有一个 T-SQL 等效于“Resume Next”或其他一些我可以在不引入大量字符串处理的情况下做到这一点的方式?

非常感谢任何帮助。

【问题讨论】:

【参考方案1】:

SQL Server 确实有 Try/Catch 语法。见:

http://msdn.microsoft.com/en-us/library/ms175976.aspx

要将它用于您的文件,您必须自己重写文件以使用 try/catch 语法包装每一行,否则您的代码必须以编程方式修改文件内容以包装每一行。

没有与“On Error Resume Next”等价的 T-SQL,感谢 Cthulhu。

【讨论】:

谢谢,很高兴知道我没有错过任何简单的东西!【参考方案2】:

实际上,您的批处理一直执行到最后,因为键违规不会干扰批处理执行。如果您从 Management Studio 运行相同的 SQL 文件,您会看到结果是所有有效语句都已执行,并且消息面板包含 each 键违规错误。 ADO.NEt 的 SqlClient 的行为方式大致相同,但在批处理结束时(当 SqlCommand.ExecuteNonQuery 返回时),它会解析返回的消息并引发异常。异常是一个 SqlException,但它的 Errors 集合包含发生的 each 键冲突的 SqlError。

不幸的是,没有灵丹妙药。理想情况下,SQL 文件不应导致错误。您可以选择遍历异常的 SqlErrors,并在知道 SQL 文件存在数据质量问题的情况下,根据个人情况确定错误是否严重,或者您可以忽略它。有些错误可能很严重,不容忽视。见Database Engine Error Severities。

另一种选择是明确告诉 SqlClient 不要抛出。如果将连接的FireInfoMessageEventOnUserErrors 属性设置为true,它将引发SqlConnection.InfoMessage 事件,而不是引发异常。

【讨论】:

FireInfoMessageEventOnUserError 设置对于性能优化非常有用。与处理 1000 多个 sqlException 不同,这是一种更快的处理方式,正是我想要的!【参考方案3】:

冒着减慢进程的风险(通过对 SQL 服务器进行数千次访问而不是一次),您可以通过将文件拆分为多个单独的查询来处理此问题,每个查询都用于 INSERT 或 UPDATE。然后,您可以捕获每个单独的错误,并按照您的业务逻辑需要记录或处理它。

【讨论】:

这几乎是我得出的结论。它慢了很多,但如果特定批次失败,我可以通过仅逐行执行来改进它。【参考方案4】:
begin try
 --your critical commands
end try
begin catch
  -- is necessary write somethink like this
  select ''
end cath

【讨论】:

【参考方案5】:

我使用的一种技术是使用 try/catch 并在 catch 内引发带有异常信息的事件。然后,调用者可以连接一个事件处理程序,对信息做任何她喜欢的事情(记录它,收集它以在 UI 中报告,等等)。

您还可以包含将 CancelableEventArgs 对象作为第二个事件参数传递的技术(用于许多 .NET 框架领域,例如 Windows.Forms 事件和 XSD 验证),事件处理程序可以设置一个布尔字段来指示毕竟该处理应该中止。

我敦促您做的另一件事是准备您的 INSERT 和 UPDATE,然后使用不同的参数多次调用它们。

【讨论】:

【参考方案6】:

我不知道下一步支持恢复的方法,但一种方法是首先使用本地表变量来防止错误,例如

Declare @Table table(id int, value varchar(100))
UPDATE mytable SET mycol2='data' WHERE mycol1=1
--Insert values for later usage
INSERT INTO @Table (id, value) VALUES (1,'data')
--Insert only if data does not already exist. 
INSERT INTO mytable (mycol1, mycol2) 
Select id, Value from @Table t left join 
mytable t2 on t.id = t2.MyCol1
where t2.MyCol is null and t.id = 1

编辑

好的,我不知道我是否建议这样做,但是如果您在所有步骤结束时设置退出条件并保持跟踪您最后执行的步骤。

Declare @Table table(id int, value varchar(100))

Declare @Step int
set @Step = 0
While (1=1)
Begin

    Begin Try

        if @Step < 1 
        Begin                 
               Insert into @Table  (id, value) values  ('s', 1)
                   Set @Step = @Step + 1
        End
        if @Step < 2
        Begin
            Insert into @Table values ( 1, 's')
                    Set @Step = @Step + 1
        End
        Break
    End Try 
    Begin Catch
      Set @Step = @Step + 1
    End Catch 

End 
Select * from @Table

【讨论】:

【参考方案7】:

不幸的是,我认为没有办法在返回错误后强制 SqlCommand 继续处理。

如果您不确定任何命令是否会导致错误(并且会降低性能),您应该将文本文件中的命令拆分为单独的 SqlCommands...这将使您能够使用 try /catch 阻止并找到有问题的语句。

...当然,这取决于文本文件中的 T-SQL 命令是否位于单独的行(或以其他方式划定)。

【讨论】:

【参考方案8】:

需要检查PK值是否已经存在。此外,一笔大交易总是比许多小交易快;并且行和页面不需要以这种方式整体锁定。

-- load a temp/import table
create table @importables (mycol1 int, mycol2 varchar(50))
insert @importables (mycol1, mycol2) values (1, 'data for 1')
insert @importables (mycol1, mycol2) values (2, 'data for 2')
insert @importables (mycol1, mycol2) values (3, 'data for 3')

-- update the base table rows that are already there
update mt set MyCol2 = i.MyCol2
from @importables i (nolock)
    inner join MyTable mt on mt.MyCol1 = i.MyCol1
where mt.MyCol2 <> i.MyCol2 -- no need to fire triggers and logs if nothing changed

-- insert new rows AFTER the update, so we don't update the rows we just inserted.
insert MyTable (mycol1, mycol2)
select mycol1, mycol2
from @importables i (nolock)
    left join MyTable mt (nolock) on mt.MyCol1 = i.MyCol1
where mt.MyCol1 is null;

您可以通过打开一个 SqlConnection、创建一个 #temp 表、使用 SqlBulkCopy 对该临时表执行 批量插入 并从那里执行增量(而不是我的 @本例中的可导入对象)。只要您使用相同的 SqlConnection,该连接上的后续查询仍可访问 #temp 表,直到您将其删除或断开连接。

【讨论】:

以上是关于SQL Server 'Resume Next' 等效项的主要内容,如果未能解决你的问题,请参考以下文章

分配给On Error Resume Next的变量的值是多少?

vbs 用On Error Resume Next跳过错误,但又想记录错误信息怎么办?

SQL Server 2012使用Offset/Fetch Next实现分页

SQL Server 中的 NEXT VALUE FOR @Sequence 的工作方式是不是与 C# 中的 Interlocked.Increment() 相同? [复制]

SQL Server Alwayson搭建二:域控配置

如何捕获sqlserver数据库的异常