【问题标题】:Is Rollback automatic in a "Using" scope with C# SQL Server calls?在 C# SQL Server 调用的“使用”范围内回滚是否自动?
【发布时间】:2013-08-25 16:19:13
【问题描述】:

当您为 SQL 连接、事务和命令创建“使用”块时,众所周知,与使用块关联的连接、事务或命令在您离开使用块后会自行正确处理堵塞。

如果在其中一个块中发生异常,例如在命令块中 - 事务是否会自行回滚,或者开发人员是否需要在命令“使用”块中执行 try catch,以及在这个try的catch中添加回滚事务语句?

【问题讨论】:

    标签: c# sql transactions using rollback


    【解决方案1】:

    不保证会被处理掉。 SqlTransactionDispose(bool) 方法实际上会有条件地回滚:

    // System.Data.SqlClient.SqlTransaction
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            SNIHandle target = null;
            RuntimeHelpers.PrepareConstrainedRegions();
            try
            {
                target = SqlInternalConnection.GetBestEffortCleanupTarget(this._connection);
                if (!this.IsZombied && !this.IsYukonPartialZombie)
                {
                    this._internalTransaction.Dispose();
                }
            }
            catch (OutOfMemoryException e)
            {
                this._connection.Abort(e);
                throw;
            }
            catch (StackOverflowException e2)
            {
                this._connection.Abort(e2);
                throw;
            }
            catch (ThreadAbortException e3)
            {
                this._connection.Abort(e3);
                SqlInternalConnection.BestEffortCleanup(target);
                throw;
            }
        }
        base.Dispose(disposing);
    }
    

    如果你注意到,它只会在this._internalTransaction.Dispose(); 被调用时发生。这里的问题是,如果GetBestEffortCleanupTarget 抛出异常,它不会被清理掉。

    在您的情况下,只要没有如前所述抛出异常,您将属于Zombied 的类别,因此它实际上会在_internalTransaction.Dispose() 调用中发出Rollback 调用。

    最后,如果用false 调用它,它肯定不会被处理掉。

    现在,除非我真的在这里遗漏了什么,否则我对这段代码的脆弱程度感到有些震惊。

    有趣的是,我认为 MSDN 文档实际上是错误的,因为它指出,对于 Rollback() 方法:

    事务只能从挂起状态回滚(在调用 BeginTransaction 之后,但在调用 Commit 之前)。 如果在调用 Commit 或 Rollback 之前处理事务,则事务会回滚。

    【讨论】:

    • GetBestEffortCleanupTarget 没有可以抛出的代码。并且“进入僵尸的类别”应该说不是僵尸。此外,如果一个事务被客户端丢弃并且XACT_ABORT被设置为ON,那么当连接被中止时server will rollback anyway,这就是为什么XACT_ABORT被推荐的原因。
    【解决方案2】:

    只要您没有成功调用 Commit,事务就会自动回滚。所以你的 using 块可能看起来像这样,如果在 Commit 之前抛出异常,事务将回滚。

    using (IDbConnection connection = ...)
    {
        connection.Open();
        using (IDbTransaction transaction = connection.BeginTransaction())
        {
            using (IDbCommand command = ...)
            {
                command.Connection = connection;
                command.Transaction = transaction;
                ...
            }
            ...
            transaction.Commit();
        }
    }
    

    【讨论】:

    • 我认为实际上存在一个不会回滚的情况。不管它多么轻微,SqlTransactionDispose(bool) 逻辑非常脆弱。
    • 我认为以上是在您的代码中使用的正确模式。显然,在某些情况下不会发生回滚 - 例如失去与数据库的连接 - 但您对此无能为力,并且数据库服务器将负责整理(这无疑包括回滚)。
    • 我可以看到你的观点朋友。如果连接丢失,您可能对数据库服务器发出回滚是正确的。我想我只是对上面的 Microsoft 代码及其脆弱程度感到有些震惊。有时它会无法回滚。此外,是的,代码的结构是完美的。
    • @TheSolution - “我对上面的 Microsoft 代码及其脆弱程度感到有点震惊” - 我不同意你的观点。我怀疑它是由一流的工程师开发的,他们仔细考虑了什么是可能的,以及你必须把什么留给数据库来整理。如果可能,应用程序开发人员可以依靠 Dispose 回滚未提交的事务。
    猜你喜欢
    • 1970-01-01
    • 2018-08-14
    • 1970-01-01
    • 1970-01-01
    • 2020-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-02
    相关资源
    最近更新 更多