【问题标题】:Does a transaction require a try catch?事务是否需要尝试捕获?
【发布时间】:2016-09-01 16:58:57
【问题描述】:

我是一名 C# 开发人员,学习更多 TSQL。我写了一个这样的脚本:

begin transaction
--Insert into several tables
end transaction

但有人告诉我这不是一个好主意并使用这样的东西:

BEGIN TRANSACTION;

BEGIN TRY
    -- Generate a constraint violation error.
    DELETE FROM Production.Product
    WHERE ProductID = 980;
END TRY
BEGIN CATCH
    SELECT 
        ERROR_NUMBER() AS ErrorNumber
        ,ERROR_SEVERITY() AS ErrorSeverity
        ,ERROR_STATE() AS ErrorState
        ,ERROR_PROCEDURE() AS ErrorProcedure
        ,ERROR_LINE() AS ErrorLine
        ,ERROR_MESSAGE() AS ErrorMessage;

    IF @@TRANCOUNT > 0
        ROLLBACK TRANSACTION;
END CATCH;

IF @@TRANCOUNT > 0
    COMMIT TRANSACTION;
GO

我不明白为什么第二个例子更正确。第一个不会以同样的方式工作吗?似乎第一个要么更新所有表,要么根本不更新?我不明白为什么在提交之前检查@@TRANCOUNT 是必要的。

【问题讨论】:

  • 我会提出与您相同的论点。此外,向您推荐的 try/catch 模式是我称之为 try/squelch 的反模式。它捕获并出错,然后静默进行。那不是处理错误,而是抑制错误。也就是说,事务不需要 try/catch 块。尤其是当您处于触发器中时,使用 try/catch 可能会导致比它所解决的问题更多的问题。
  • 如果在第二个示例中有任何内容,提交应该在 try 块中,而不是在 catch 之后......我认为

标签: sql-server tsql


【解决方案1】:

只有当你在 try 块中并且在实际语句之前打开一个事务,并直接提交它,不要等待你的控件进入批处理的末尾来提交你的事务。

一旦您进入 Try Block 并打开了一个事务,如果出现问题,控件将跳转到 CATCH 块,只需在此处回滚您的事务并根据需要进行其他错误处理。

在使用@@ROWCOUNT 函数实际回滚任何打开事务的事务检查之前,我添加了一个小检查,在这种情况下它并没有多大意义。当您在打开事务之前在 try 块中进行一些验证检查时,它更有用,例如检查参数值和其他内容,如果任何验证检查失败,则在 try 块中引发错误,在这种情况下,控制将跳转到 catch 块甚至无需在那里打开交易,您就可以检查任何打开的交易并回滚是否有任何打开的交易。在您的情况下,您实际上不需要检查任何未结交易,因为除非您的交易中出现问题,否则您不会进入 catch 块。

BEGIN TRY

  BEGIN TRANSACTION 
     -- Multiple Inserts
    INSERT INTO....
    INSERT INTO.... 
    INSERT INTO.... 
 COMMIT TRANSACTION 
    PRINT 'Rows inserted successfully...'

END TRY

BEGIN CATCH 
  IF (@@TRANCOUNT > 0)
   BEGIN
      ROLLBACK TRANSACTION 
      PRINT 'Error detected, all changes reversed'
   END 
    SELECT
        ERROR_NUMBER() AS ErrorNumber,
        ERROR_SEVERITY() AS ErrorSeverity,
        ERROR_STATE() AS ErrorState,
        ERROR_PROCEDURE() AS ErrorProcedure,
        ERROR_LINE() AS ErrorLine,
        ERROR_MESSAGE() AS ErrorMessage
END CATCH

【讨论】:

  • 我认为您在解释中指的是@@TranCount,而不是@@RowCount。我也不愿在一般消息中包含“所有更改都已反转”,因为可能存在“丢失”身份值、触发器引起的副作用……。
【解决方案2】:

我想在这里作为 C# 开发人员发表我的看法:

在上面给出的简单场景中(只是从脚本中插入几个表),没有理由添加 try/catch,因为它对事务没有任何好处。这两个示例将产生完全相同的结果:要么插入所有表,要么不插入。数据库的状态保持一致。 (由于从不调用 COMMIT TRANSACTION,所以回滚由 Sql Server 在脚本末尾隐式调用。)

但是,有时您可以在 try/catch 中执行集成错误处理无法完成的操作。例如,将错误记录到错误表中。

在我的 C# 经验中,使用 Try/Catch 的唯一时间是在开发人员无法控制的情况下使用,例如尝试打开文件。在这种情况下,管理 .Net 框架生成的异常的唯一方法是通过 Try/Catch。

如果我正在执行存储过程,并且想要手动检查数据状态并手动调用ROLLBACK TRANSACTION,我可以看到。但它仍然不需要 try/catch。

【讨论】:

  • 是什么让您认为如果不调用 COMMIT 会自动调用 ROLLBACK?我认为你错了,除非你的 C# 程序使用短期连接并在当前工作单元完成后立即关闭它们。在这种情况下,MSSQL 会在您关闭 C# 中的连接时执行ROLLBACK,但这只是一种可能的方法。在我的大多数 C# 程序中,与 MSSQL 的连接都具有程序的生命周期,因此我必须非常小心地处理事务。我什至为嵌套事务编写了一个包装器,以使我的方法更舒适。
【解决方案3】:

我对 T-SQL 中的 TRY...CATCH 有两种看法。

虽然它是对语言的潜在有用的补充,但它可用的事实并不总是使用它的理由。

拿走你的

DELETE FROM Table WHERE...

示例(我意识到这只是一个示例)。唯一会因错误而失败的方法是代码严重偏离模式。 (例如,如果有人在它的 PK 端创建了一个带有 Table 的外键)。

经过适当的测试,代码和架构之间的这种不匹配绝不应该将其投入生产。假设确实如此,恕我直言,将导致的“粗鲁”、无中介的错误消息比“礼貌”地将其包装到 SELECT 语句中以返回给客户端更好地指示出了什么问题。 (这相当于 SeanLange 提到的 try/squelch 反模式)。

对于更复杂的场景,我可以看到 TRY...CATCH 的用途。虽然恕我直言,但它不能替代仔细验证输入参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-22
    • 2016-02-10
    • 1970-01-01
    • 2016-05-11
    • 2012-11-02
    • 1970-01-01
    • 2022-07-05
    • 1970-01-01
    相关资源
    最近更新 更多