在SQL 2005存储过程中添加错误处理的最佳方法是什么?


11

什么是使存储的proc足够健壮以使其可以很好地扩展并包含错误处理的好方法?

此外,在存储的proc中处理多个错误情况并拥有智能反馈系统的最佳方法是什么,该系统将有意义的错误信息返回给调用的应用程序?


2
尝试在SQL Server 2005中使用更新的TRY CATCH块。sommarskog.se/error_handling_2005.html
Sankar Reddy

@Kacalapy,您好〜我以后会建议您自己问每一个问题,这样我们就可以一次针对一个问题提出具体的答案。我鼓励您使用这个问题。
jcolebrand

Answers:


12

亚历克斯·库兹涅佐夫(Alex Kuznetsov)在其《防御性数据库编程》(第8章)中有很长的一章,内容涉及T-SQL TRY ... CATCH,T-SQL事务和SET XACT_ABORT设置,以及使用客户端错误处理。这将帮助您确定哪种选择最适合您需要完成的工作。

它可用于免费这个网站。我绝不隶属于该公司,但我确实拥有该书的印刷版。

亚历克斯很好地解释了这个问题上的许多小细节。

根据Nick的要求...(但并非所有内容都在本章中)

在扩展方面,您必须坦率地说哪些活动需要在数据库代码中,哪些活动应在应用程序中。是否曾经注意到快速执行的代码趋向于回到针对每种方法进行单个关注的设计?

最简单的通信方式是自定义错误代码(> 50,000)。它也非常快。这确实意味着您必须使数据库代码和应用程​​序代码保持同步。使用自定义错误代码,您还可以在错误消息字符串中返回有用的信息。因为您严格地针对这种情况使用了错误代码,所以您可以在应用程序代码中为错误的数据格式量身定制解析器。

另外,哪些错误条件需要数据库中的重试逻辑?如果您想在X秒钟后重试,那么最好在应用程序代码中进行处理,以免事务阻塞太多。如果您只是立即重新提交DML操作,则在SP中重复执行它可能会更有效率。但是请记住,您可能必须复制代码或添加一层SP才能重试。

确实,这是目前SQL Server中TRY ... CATCH逻辑的最大难题。可以做到,但是有点麻烦。在SQL Server 2012中寻找一些改进,尤其是重新抛出系统异常(保留原始错误号)。另外,还有FORMATMESSAGE,它在构造错误消息时增加了一些灵活性,尤其是用于记录目的。


很好的建议和一本非常好的书!
玛丽安

Red Gate提供了几本非常有用的免费电子书,而这本无疑是更好的电子书之一。很棒的建议。
Matt M

并不是所有的书都这样做,但是库兹涅佐夫(Kuznetsov)的“防御...”书的免费版本没有包含有关事务隔离级别和开发可在并发中进行的修改的最后两章。为了我。那里的内容值得购买。
Phil Helmer

7

这是我们的模板(错误记录已删除)

笔记:

  • 没有XACT_ABORT,所有TXN开始和提交/回滚必须成对
  • 提交递减@@ TRANCOUNT
  • 回滚将@@ TRANCOUNT返回零,所以您会收到错误266
  • 您不能仅回滚当前层(例如,回滚时递减@@ TRANCOUNT)
  • XACT_ABORT抑制错误266
  • 每个存储的proc必须遵循相同的模板,因此每个调用都是原子的
  • 由于XACT_ABORT,回滚检查实际上是多余的。但是,它使我感觉好些,没有时看起来很奇怪,并允许您不想使用它
  • 这允许客户端TXN(如LINQ)
  • Remus Rusanu具有使用保存点的类似外壳。我更喜欢原子数据库调用,并且不像他们的文章那样使用部分更新

...因此创建的TXN不会超出您的需要

然而,

CREATE PROCEDURE [Name]
AS
SET XACT_ABORT, NOCOUNT ON

DECLARE @starttrancount int

BEGIN TRY
    SELECT @starttrancount = @@TRANCOUNT

    IF @starttrancount = 0
        BEGIN TRANSACTION

       [...Perform work, call nested procedures...]

    IF @starttrancount = 0 
        COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF XACT_STATE() <> 0 AND @starttrancount = 0 
        ROLLBACK TRANSACTION
    RAISERROR [rethrow caught error using @ErrorNumber, @ErrorMessage, etc]
END CATCH
GO

如果@@ TRANCOUNT大于0怎么办?您不做任何工作或没有任何反馈?
kacalapy 2011年

@kacalapy:没有嵌套交易之类的东西,因此我们不会再启动另一个scribd.com/doc/49579859/33/Nested-Transactions-Are-Real
gbn

3

我使用“尝试/捕获”,但我还会收集尽可能多的信息,并在回滚后将其写入错误日志。在此示例中,“ LogEvent”是一个存储过程,它写入EventLog表,其中包含发生的事件的详细信息。GetErrorInfo()是一个函数调用,返回确切的错误消息。

当发生错误时,将收集信息,该过程将跳至错误处理部分并发出回滚。该信息将被写入日志,然后该过程退出。

考虑到所涉及的额外过程/函数调用,这似乎有点过头了。但是,在尝试调试问题时,此方法非常有用。

exec LogEvent @ Process,@ Database,“尝试插入等等等等”
开始尝试
  插入MyTable
  选择值
    从MyOtherTable

  选择@rowcount = @@ ROWCOUNT
结束尝试
-错误处理
开始比赛
  选择@error = ERROR_NUMBER(),
         @rowcount = -1,
         @TableAction ='插入',
         @TableName = @数据库+'.MyTable',
         @AdditionalInfo ='(尝试插入等等)'+ dbo.GetErrorInfo()
   转到TableAccessError
结束观看

。
。
。
。

TableAccessError:
如果(@@ TRANCOUNT> 0)回滚
选择@output = upper(@TableAction)+ 
       '错误-'+时发生错误 
       案例(@TableAction)
         当“更新”然后“更新”
         当“删除”然后“删除”
         否则@TableAction +'ing'
       结束+ 
       '记录'+ 
       案例(@TableAction) 
         当“选择”然后“从” 
         当'更新'然后'在' 
         当“插入”然后“插入”时
         其他“来自”   
         结束+ 
         ''+ @TableName +'表。
选择@output = @output +'@@ ERROR:'+ convert(varchar(8),@ error) 
选择@output = @output +'@@ ROWCOUNT:'+ convert(varchar(8),@ rowcount) 

选择@output = @output + isull(@AdditionalInfo,'')
exec LogEvent @ Process,@ Database,@ Output
RAISERROR(@ output,16,1)与日志
选择@ReturnCode = -1
转到THE_EXIT


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.