TransactionScope 过早完成

Posted

技术标签:

【中文标题】TransactionScope 过早完成【英文标题】:TransactionScope Prematurely Completed 【发布时间】:2011-02-24 16:50:00 【问题描述】:

我有一个在 TransactionScope 内运行的代码块,在这个代码块中我对数据库进行了多次调用。选择、更新、创建和删除,整个范围。当我执行删除时,我使用 SqlCommand 的扩展方法执行它,如果它死锁,它将自动重新提交查询,因为此查询可能会遇到死锁。

我相信当遇到死锁并且函数尝试重新提交查询时会出现问题。这是我收到的错误:

与当前连接关联的事务已完成但尚未处理。必须先释放事务,然后才能使用连接执行 SQL 语句。

这是执行查询的简单代码(下面的所有代码都在 TransactionScope 的使用范围内执行):

using (sqlCommand.Connection = new SqlConnection(ConnectionStrings.App))

    sqlCommand.Connection.Open();
    sqlCommand.ExecuteNonQueryWithDeadlockHandling();

这里是重新提交死锁查询的扩展方法:

public static class SqlCommandExtender

    private const int DEADLOCK_ERROR = 1205;
    private const int MAXIMUM_DEADLOCK_RETRIES = 5;
    private const int SLEEP_INCREMENT = 100;

    public static void ExecuteNonQueryWithDeadlockHandling(this SqlCommand sqlCommand)
    
        int count = 0;
        SqlException deadlockException = null;

        do
        
            if (count > 0) Thread.Sleep(count * SLEEP_INCREMENT);
            deadlockException = ExecuteNonQuery(sqlCommand);
            count++;
        
        while (deadlockException != null && count < MAXIMUM_DEADLOCK_RETRIES);

        if (deadlockException != null) throw deadlockException;
    

    private static SqlException ExecuteNonQuery(SqlCommand sqlCommand)
    
        try
        
            sqlCommand.ExecuteNonQuery();
        
        catch (SqlException exception)
        
            if (exception.Number == DEADLOCK_ERROR) return exception;
            throw;
        

        return null;
    

错误发生就行了:

sqlCommand.ExecuteNonQuery();

【问题讨论】:

【参考方案1】:

不要忘记从 TransactionScope 中禁止选择语句。在 SQL Server 2005 及更高版本中,即使您使用 with(nolock),仍然会在 select 触及的那些表上创建锁。看看这个,它会告诉你how to setup and use TransactionScope。

using(TransactionScope ts = new TransactionScope 
 
  // db calls here are in the transaction 
  using(TransactionScope tsSuppressed = new TransactionScope (TransactionScopeOption.Suppress)) 
   
    // all db calls here are now not in the transaction 
   
 

【讨论】:

感谢有关在 select 语句中抑制事务的提示。这有助于解决让我抓狂的超时问题。 很棒的答案。这让我对 sql 指令的选择/插入集合感到疯狂。添加 Suppress 选项会自动解决问题。 天哪,谢谢!我整天都在为此苦苦挣扎。如此简单的解决方案。 过度锁定通常是默认Serializable TS 隔离级别的结果。这通常最好通过将 TS 创建包装在一个 classfactory 中来解决,该 classfactory 将序列化设置为更多sane like Read Comitted 我不明白。这与错误消息有什么关系?链接的文章是关于分布式事务的。以及为什么要抑制任何内容 - 锁定多少由隔离级别决定。【参考方案2】:

我发现当事务运行的时间长于System.TransactionsmaxTimeout 时,可能会出现此消息。 TransactionOptions.Timeout增加没关系,不能超过maxTimeout

maxTimeout的默认值设置为10分钟,其值只能machine.config中修改

machine.config 中添加以下内容(在配置级别)以修改超时:

<configuration>
    <system.transactions>
        <machineSettings maxTimeout="00:30:00" />
    </system.transactions>
</configuration>

machine.config 位于:%windir%\Microsoft.NET\Framework\[version]\config\machine.config

您可以在这篇博文中了解更多信息:http://thecodesaysitall.blogspot.se/2012/04/long-running-systemtransactions.html

【讨论】:

请记住,配置文件区分大小写,因此它应该是“machineSettings”和“maxTimeout”。太糟糕了,你不能在你的 app.config 文件中覆盖它:( 另外注意你必须把它放在config部分的end,否则会报错。【参考方案3】:

我可以重现该问题。这是事务超时。

using (new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 0, 0, 1)))

    using (SqlConnection connection = new SqlConnection(connectionString))
    
        connection.Open();
        using (var sqlCommand = connection.CreateCommand())
        
            for (int i = 0; i < 10000; i++)
            
                sqlCommand.CommandText = "select * from actor";
                using (var sqlDataReader = sqlCommand.ExecuteReader())
                
                    while (sqlDataReader.Read())
                    
                    
                
            
        
    

抛出 System.InvalidOperationException 并发送此消息:

与当前连接相关的事务已经完成 但尚未处置。交易必须在之前处理 该连接可用于执行 SQL 语句。

为了解决这个问题,让您的查询运行得更快或增加超时时间。

【讨论】:

【参考方案4】:

如果在 TransactionScope 中发生异常,它会被回滚。这意味着TransactionScope 已完成。您现在必须调用dispose() 并开始一个新事务。老实说,我不确定您是否可以重复使用旧的 TransactionScope,我从未尝试过,但我认为不会。

【讨论】:

即使捕获到异常事务也会回滚? 我从来没有像我一样尝试过它,异常 = 错误 = 停止和回滚。但是,从您所描述的情况来看,似乎是这样。 这是错误的,不是发生在异常上的情况。您也不必调用 Dispose() 。当在 using 语句中生成 TransactionScope 时,using 语句将 Dispose() TransactionScope on Exceptions。 在 Dispose() 上,TransactionScope 将回滚或提交,具体取决于天气 TransactionScope.Complete() 是否被调用。这就是为什么在结束 using 块之前必须将 Complete() 作为最后一件事调用的原因。您当然也可以手动使用 try-finally 阻止 Dispose()。然而,这不会改变任何关于 Dispose()、异常、回滚行为的假设错误。抱歉,不得不投反对票。 我也有同样的问题,但可以理解答案。我是这样给的。使用 (TransactionScope 范围 = new TransactionScope(TransactionScopeOption.RequiresNew))【参考方案5】:

我的问题是一个愚蠢的问题,如果您通过超时进行调试中断,您将得到这个。 掌心

伙计,编程让你有些日子感觉很沉重......

【讨论】:

【参考方案6】:

已确认此错误也可能是由事务超时引起的。只是为了补充 Marcus + Rolf 所说的,如果您没有在 TransactionScope 上明确设置超时,超时 TimeSpan 将采用默认值。此默认值是以下的较小

    如果您已覆盖本地 app.config / web.config 设置,例如

    <system.transactions>
    <defaultSettings timeout="00:05:00" />
    </system.transactions>
    

    但这会在machine.config 设置&lt;machineSettings maxTimeout="00:10:00" /&gt; 处“封顶”

【讨论】:

我可以在 app.config 文件覆盖这个吗?【参考方案7】:

此异常也可能由禁用 Microsoft Distributed Transaction Coordinator 引起。

如果我们想启用它,我们运行“dcomcnfg”并选择"Component Services" -&gt; "My Computer" -&gt; "Distributed Transaction Coordinator" -&gt; "Local Service DTC" 并选择“Properties”。

应勾选“允许远程客户端”、“允许入站”、“允许出站”和“不验证必填”。

【讨论】:

您可以启用/禁用 以编程方式 使用BAT or ps1 吗?

以上是关于TransactionScope 过早完成的主要内容,如果未能解决你的问题,请参考以下文章

如何在多实例应用程序上处理 TransactionScope?

TransactionScope事务

反应 redux,thunk 完成事件过早发生

TransactionScope 事务 = new TransactionScope() VS TransactionScope s = context.Connection.BeginTransac

为啥 TransactionScope 不能与 Sqlite 一起使用?

如何加入 TransactionScope?