【问题标题】:Exception handling around the rollback of a SqlTransaction围绕 SqlTransaction 回滚的异常处理
【发布时间】:2009-06-03 09:45:41
【问题描述】:

我有两个要在事务中执行的存储过程。由于各种原因,我需要在我的应用程序代码中而不是在数据库中处理事务。

目前,我的代码如下所示:

try
{
    using (SqlConnection conn = Connection())
    {
        conn.Open();

        using (SqlTransaction sqlTrans = conn.BeginTransaction())
        {
            try
            {
                using (SqlCommand cmd1 = new SqlCommand("Stored_Proc_1", conn, sqlTrans))
                {
                    cmd1.CommandType = CommandType.StoredProcedure;
                    cmd1.ExecuteNonQuery();
                }

                using (SqlCommand cmd2 = new SqlCommand("Stored_Proc_2", conn, sqlTrans))
                {
                    cmd2.CommandType = CommandType.StoredProcedure;
                    cmd2.ExecuteNonQuery();
                }

                sqlTrans.Commit();
            }
            catch
            {
                    sqlTrans.Rollback();

                    throw;
            }

        }

        conn.Close();
    }
}

catch (SqlException ex)
{
  // exception handling and logging code here...
}

当其中一个存储过程引发错误时,我看到的异常消息如下所示:

Error message from raiserror within stored procedure.
Transaction count after EXECUTE indicates that a COMMIT or ROLLBACK TRANSACTION statement is missing. Previous count = 1, current count = 0.

这是有道理的,因为在第一次捕获时,事务还没有回滚。

但我想要一个“干净”的错误(没有 tran count 消息 - 我对此不感兴趣,因为我 am 回滚事务)用于我的异常处理代码。 有没有办法可以重组我的代码来实现这一点?

编辑:

我的存储过程的基本结构如下所示:

create proc Stored_Proc_1
as

set nocount on

begin try
    begin transaction 

        raiserror('Error raised by Stored_Proc_1', 16, 1)       

    commit

end try
begin catch  
    if (@@trancount > 0) rollback   

    declare @ErrMsg nvarchar(4000), @ErrSeverity int, @ErrProc sysname, @ErrLine varchar(10)
    select @ErrMsg = ERROR_MESSAGE(), @ErrSeverity = ERROR_SEVERITY(), @ErrProc = ERROR_PROCEDURE(), @ErrLine = ERROR_LINE()

    -- log the error 
    -- sql logging code here...

    raiserror(@ErrMsg, @ErrSeverity, 1) 
end catch

更新: 我已经从我的存储过程中取出了事务处理,这似乎已经解决了这个问题。显然我做错了——但我仍然想知道如何做对。从存储过程中删除事务是最好的解决方案吗?

【问题讨论】:

    标签: c# .net sql transactions


    【解决方案1】:

    好吧,conn.Close() 无论如何都可以去 - 它会被 using 关闭(如果你想一想,奇怪的是我们在异常之后才 Close() 它)。

    您的任何一个存储过程是否在其内部执行任何事务代码(未回滚/提交)?听起来那个是问题出在哪里......?如果有的话,错误消息向我表明其中一个存储过程正在执行COMMIT,即使它没有启动事务 - 可能是由于(不正确的)方法:

    -- pseduo-TSQL
    IF @@TRANCOUNT = 0 BEGIN TRAN
    -- ...
    IF @@TRANCOUNT > 0 COMMIT TRAN -- or maybe = 1
    

    (如果您在 TSQL 中执行条件事务,您应该(通过 bool 标志)跟踪您是否创建了事务 - 并且只有 COMMIT 如果您这样做了)

    另一种选择是使用TransactionScope - 更易于使用(您无需针对每个命令等进行设置),但效率略低

    using(TransactionScope tran = new TransactionScope()) {
        // create command, exec sp1, exec sp2 - without mentioning "tran" or
        // anything else transaction related
    
        tran.Complete();
    }
    

    (注意没有回滚等;Dispose()(通过using)将在需要时进行回滚。

    【讨论】:

    • 感谢您的 cmets,我不认为我存储的 proc 行为异常 - 我已经更新了问题以包含它们的基本结构。
    • 等等,我想我明白你在说什么了 - c# 代码期望事务存在,但 SQL 代码正在回滚它?
    • 正是...“if (@@trancount > 0) rollback”这一行是赠品...它应该设置一个标志来跟踪 it 是否创建了事务,而不是仅仅检查是否有一个未结交易。
    【解决方案2】:

    如果您在应用程序中执行此操作,请不要在您的数据库/存储过程中执行事务!这肯定只会造成混乱。选择一层并坚持下去。确保您有一个很好的规范化数据库,并且异常应该向上渗透。

    【讨论】:

      【解决方案3】:

      我同意 Marc 的观点,即问题可能出在存储过程本身。 有一篇非常有趣的文章概述了一些问题here

      【讨论】:

        【解决方案4】:

        如果存储过程包含这样的代码:

        BEGIN TRY
            SET @now = CAST(@start AS datetime2(0))
        END TRY
        BEGIN CATCH
            SET @now = CURRENT_TIMESTAMP
        END CATCH
        

        然后你通过了,例如'now' 作为@start,try 中的 CAST 将失败。即使错误本身已被捕获和处理,这也将事务标记为回滚。因此,尽管您从上述代码中没有发现异常,但无法提交事务。如果你的存储过程有这样的代码,需要重写以避免try/catch。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-10-03
          • 1970-01-01
          相关资源
          最近更新 更多