代码之家  ›  专栏  ›  技术社区  ›  Mike

交易范围和交易

  •  17
  • Mike  · 技术社区  · 16 年前

    在我的C代码中,我使用TransactionScope是因为我被告知不要依赖于我的SQL程序员将始终使用事务,我们对此负责,Yada Yada。

    这么说了

    看起来TransactionScope对象在sqlTransaction之前回滚?是否可能,如果可能,在事务中包装TransactionScope的正确方法是什么?

    这是SQL测试

    CREATE PROC ThrowError
    AS
    
    BEGIN TRANSACTION --SqlTransaction
    SELECT 1/0
    
    IF @@ERROR<> 0
    BEGIN
      ROLLBACK TRANSACTION --SqlTransaction
      RETURN -1 
    END
    ELSE
    BEGIN
      COMMIT TRANSACTION --SqlTransaction
      RETURN 0
    END
    
    go
    
    DECLARE @RESULT INT
    
    EXEC @RESULT = ThrowError
    
    SELECT @RESULT
    

    如果我运行这个,我得到除以0的结果,然后返回-1

    从C代码调用我会收到一条额外的错误消息

    遇到被零除错误。
    执行后的事务计数表示缺少提交或回滚事务状态。上一个计数=1,当前计数=0。

    如果我给SQL事务一个名称,那么

    无法回滚SQLTransaction。 找不到该名称的事务或保存点。 执行后的事务计数指示提交或回滚 缺少事务语句。前一个计数=1,当前计数=2。

    有时计数似乎会增加,直到应用程序完全退出。

    C是公正的

            using (TransactionScope scope = new TransactionScope())
            {
                 ... Execute Sql 
    
                 scope.Commit()
             }
    

    编辑:

    SQL代码必须适用于2000和2005年

    6 回复  |  直到 13 年前
        1
  •  22
  •   hoang    13 年前

    在SQL Server 2005中对错误处理进行了大规模升级。这些文章相当广泛: Error Handling in SQL 2005 and Later by Erland Sommarskog Error Handling in SQL 2000 – a Background by Erland Sommarskog

    最好的办法是这样:

    创建存储过程如下:

    CREATE PROCEDURE YourProcedure
    AS
    BEGIN TRY
        BEGIN TRANSACTION --SqlTransaction
        DECLARE @ReturnValue int
        SET @ReturnValue=NULL
    
        IF (DAY(GETDATE())=1 --logical error
        BEGIN
            SET @ReturnValue=5
            RAISERROR('Error, first day of the month!',16,1) --send control to the BEGIN CATCH block
        END
    
        SELECT 1/0  --actual hard error
    
        COMMIT TRANSACTION --SqlTransaction
        RETURN 0
    
    END TRY
    BEGIN CATCH
        IF XACT_STATE()!=0
        BEGIN
            ROLLBACK TRANSACTION --only rollback if a transaction is in progress
        END
    
        --will echo back the complete original error message to the caller
        --comment out if not needed
        DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
    
        SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
        RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
    
        RETURN ISNULL(@ReturnValue,1)
    
    END CATCH
    
    GO
    

    但是,这仅适用于SQL Server 2005及更高版本。如果不使用SQL Server 2005中的Try-Catch块,则很难删除SQL Server返回的所有消息。这个 extra messages 您所引用的是由使用@@TranCount处理回滚的性质所导致的:

    http://www.sommarskog.se/error-handling-I.html#trancount

    @@trancount是一个全局变量, 反映嵌套的级别 交易。每个开始事务 将@@TranCount增加1,每个增加1 提交事务减少 @@按1传输计数。实际上什么都不是 提交到@@TranCount达到0。 回滚事务回滚 一切从最外面开始 交易(除非您使用 相当奇特的储蓄交易),以及 强制@@TranCount为0,考虑到 上一个值。

    当退出存储过程时,如果 @@TranCount不具有相同的 当程序 开始执行,SQL Server引发 错误266。未出现此错误, 但是,如果调用该过程 从触发器直接或 间接地。如果 您正在使用set implicit运行 交易

    如果不希望收到有关事务计数不匹配的警告,则每次只需要打开一个事务。您可以通过创建以下所有过程来完成此操作:

    CREATE PROC YourProcedure
    AS
    DECLARE @SelfTransaction char(1)
    SET @SelfTransaction='N'
    
    IF @@trancount=0
    BEGIN
        SET @SelfTransaction='Y'
        BEGIN TRANSACTION --SqlTransaction
    END
    
    SELECT 1/0
    
    IF @@ERROR<> 0
    BEGIN
        IF @SelfTransaction='Y'
        BEGIN
            ROLLBACK TRANSACTION --SqlTransaction
        END
        RETURN -1 
    END
    ELSE
    BEGIN
        IF @SelfTransaction='Y'
        BEGIN
            COMMIT TRANSACTION --SqlTransaction
        END
        RETURN 0
    END
    
    GO
    

    通过这样做,仅当您还没有在事务中时才发出事务命令。如果您以这种方式对所有过程进行编码,那么只有发出begin事务的过程或C代码才会实际发出commit/rollback,并且事务计数总是匹配的(您不会得到错误)。

    在C语言中 TransactionScope Class Documentation :

    static public int CreateTransactionScope(
        string connectString1, string connectString2,
        string commandText1, string commandText2)
    {
        // Initialize the return value to zero and create a StringWriter to display results.
        int returnValue = 0;
        System.IO.StringWriter writer = new System.IO.StringWriter();
    
        try
        {
            // Create the TransactionScope to execute the commands, guaranteeing
            // that both commands can commit or roll back as a single unit of work.
            using (TransactionScope scope = new TransactionScope())
            {
                using (SqlConnection connection1 = new SqlConnection(connectString1))
                {
                    // Opening the connection automatically enlists it in the 
                    // TransactionScope as a lightweight transaction.
                    connection1.Open();
    
                    // Create the SqlCommand object and execute the first command.
                    SqlCommand command1 = new SqlCommand(commandText1, connection1);
                    returnValue = command1.ExecuteNonQuery();
                    writer.WriteLine("Rows to be affected by command1: {0}", returnValue);
    
                    // If you get here, this means that command1 succeeded. By nesting
                    // the using block for connection2 inside that of connection1, you
                    // conserve server and network resources as connection2 is opened
                    // only when there is a chance that the transaction can commit.   
                    using (SqlConnection connection2 = new SqlConnection(connectString2))
                    {
                        // The transaction is escalated to a full distributed
                        // transaction when connection2 is opened.
                        connection2.Open();
    
                        // Execute the second command in the second database.
                        returnValue = 0;
                        SqlCommand command2 = new SqlCommand(commandText2, connection2);
                        returnValue = command2.ExecuteNonQuery();
                        writer.WriteLine("Rows to be affected by command2: {0}", returnValue);
                    }
                }
    
                // The Complete method commits the transaction. If an exception has been thrown,
                // Complete is not  called and the transaction is rolled back.
                scope.Complete();
            }
        }
        catch (TransactionAbortedException ex)
        {
            writer.WriteLine("TransactionAbortedException Message: {0}", ex.Message);
        }
        catch (ApplicationException ex)
        {
            writer.WriteLine("ApplicationException Message: {0}", ex.Message);
        }
    
        // Display messages.
        Console.WriteLine(writer.ToString());
    
        return returnValue;
    }
    

    只是一个想法,但是你可以使用 TransactionAbortedException catch获取实际错误并忽略事务计数不匹配警告。

        2
  •  13
  •   Hans Passant    16 年前

    不在中使用事务 二者都 你的C代码 链轮。一个就够了。这几乎总是应该是您的C代码,只有它知道应该拒绝或提交对数据库的全部更新。

        3
  •  2
  •   gbn    16 年前

    如果必须支持SQL Server 2000,请使用TransactionScope使您的生活更轻松。但是,请参阅底部了解它为什么有限制。

    Try/Catch之前的SQL错误处理是错误的。KM发表的Erland文章解释了语句/作用域/批中止错误。基本上,代码可能只是停止执行,而您只剩下行上的锁等。

    这就是上面发生的事情,所以您的回滚不会运行,所以您会得到关于事务计数的错误226。

    如果您只支持SQL Server 2005+,那么请使用Try/Catch来捕获所有错误,并使用set xact_abort on。Try/Catch使SQL Server更具弹性,并捕获所有运行时错误。设置xact_abort on也会抑制错误226,因为它会自动发出回滚 确保释放所有锁。

    顺便说一句:

    选择1/0是一个很好的例子,说明了为什么应该使用SQL错误处理。

    使用数据适配器填充

    • 存储过程中的数据表,选择1/0->未捕获错误
    • 存储过程中包含select 1/0->错误的数据集

    SQL Try/Catch将处理此问题…

        4
  •  1
  •   Community Mohan Dere    9 年前

    你应该试试看

    BEGIN TRANSACTION --SqlTransaction
    BEGIN TRY
        SELECT 1/0
        COMMIT TRANSACTION --SqlTransaction
        RETURN 0
    END TRY
    BEGIN CATCH
      ROLLBACK TRANSACTION --SqlTransaction
      RETURN -1 
    END CATCH
    

    这个问题应该回答你关于交易范围和回滚的问题 How does TransactionScope roll back transactions?

        5
  •  0
  •   akjoshi HCP    13 年前
    public string ExecuteReader(string SqlText)
    {
        SqlCommand cmd;
        string retrunValue = "";
        try
        {
            c.Open();
            cmd = new SqlCommand();
            cmd.CommandType = CommandType.Text;                
            cmd.Connection = c;
            cmd.CommandText = SqlText;
            retrunValue = Convert.ToString(cmd.ExecuteScalar());
            c.Close();
        }
        catch (Exception SqlExc)
        {
            c.Close();
            throw SqlExc;
    
        }
        return (retrunValue);
    }
    
        6
  •  -1
  •   Mark    16 年前

    我知道这是一个非常普通的建议,但首先防止被零除不是一个很好的解决方案吗?几乎所有DML操作(insert、select、update)都可以重写,以避免在使用case语句时被零除。