.NET 8+EF Core事务超时异常:已设长超时仍15分钟失败求助
在IIS部署的.NET 8 + EF Core项目中执行4个存储过程,已将TransactionScope事务超时设为1小时,DbContext命令超时设为45分钟,但总是在约15分钟后因事务超时终止。这些存储过程在SSMS中的总执行时间为37分钟。
DbContext配置
int timeout = Convert.ToInt32(new TimeSpan(0, 45, 0).TotalSeconds); options.UseSqlServer(connectionstring, x => x.CommandTimeout(timeout)) //timeout
业务代码(MediatR请求处理器中执行)
private static readonly string logLineStartedExecutingMethod = "Started executing method {MethodName}"; private static readonly string logLineFinishedExecutingMethod = "Finished executing method {MethodName} - {RecordCount}"; using var trans = new TransactionScope(TransactionScopeOption.RequiresNew, new TimeSpan(1, 0, 0), TransactionScopeAsyncFlowOption.Enabled); _logger.LogInformation(logLineStartedExecutingMethod, "UspTestA"); var resultA = await _procedureRepository.UspTestAAsync(default, cancellationToken); _logger.LogInformation(logLineFinishedExecutingMethod, "UspTestA", resultA?.ToString() ?? "0"); _logger.LogInformation(logLineStartedExecutingMethod, "UspTestB"); var resultB = await _procedureRepository.UspTestBAsync(default, cancellationToken); _logger.LogInformation(logLineFinishedExecutingMethod, "UspTestB", resultB?.ToString() ?? "0"); _logger.LogInformation(logLineStartedExecutingMethod, "UspTestC"); var resultC = await _procedureRepository.UspTestCAsync(request.UserName ?? "Onbekend", default, cancellationToken); _logger.LogInformation(logLineFinishedExecutingMethod, "UspTestC", resultC?.ToString() ?? "0"); _logger.LogInformation(logLineStartedExecutingMethod, "UspTestD"); // ERROR!!! Transaction timeout after approx. 15 minutes var resultD = await _procedureRepository.UspTestDAsync(default, cancellationToken); _logger.LogInformation(logLineFinishedExecutingMethod, "UspTestD", resultD?.ToString() ?? "0"); trans.Complete();
异常信息
System.Transactions.TransactionException: 该操作对于事务的状态无效。
System.TimeoutException: 事务超时
--- 内部异常堆栈跟踪结束 ---在 System.Transactions.TransactionState.EnlistPromotableSinglePhase(InternalTransaction tx, IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Transaction atomicTransaction, Guid promoterType)
在 System.Transactions.Transaction.EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification, Guid promoterType)
在 System.Transactions.Transaction.EnlistPromotableSinglePhase(IPromotableSinglePhaseNotification promotableSinglePhaseNotification)
在 Microsoft.Data.SqlClient.SqlInternalConnection.EnlistNonNull(Transaction tx)
在 Microsoft.Data.ProviderBase.DbConnectionPool.PrepareConnection(DbConnection owningObject, DbConnectionInternal obj, Transaction transaction)
在 Microsoft.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
在 Microsoft.Data.ProviderBase.DbConnectionPool.WaitForPendingOpen()
--- 上一位置中堆栈跟踪结束 ---
在 Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenInternalAsync(Boolean errorsExpected, CancellationToken cancellationToken)
在 Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenInternalAsync(Boolean errorsExpected, CancellationToken cancellationToken)
在 Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected)
在 Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
在 Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) 在 Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func4 operation, Func4 verifySucceeded, CancellationToken cancellationToken) 在 Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable1.AsyncEnumerator.MoveNextAsync()
在 Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) 在 Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable1 source, CancellationToken cancellationToken)
在 Data.StoredProcedures.DbContextExtensions.SqlQueryAsync[T](DbContext db, String sql, Object[] parameters, CancellationToken cancellationToken)
核心原因
代码里设置的TransactionScope超时被系统层面的限制覆盖了:
- MSDTC默认超时:Windows分布式事务协调器(MSDTC)默认事务超时为15分钟,当事务因多连接操作被提升为分布式事务时,会触发这个系统级限制。
- IIS应用池回收/闲置超时:IIS应用池默认闲置超时和定时回收间隔也在15分钟左右,会直接终止进程导致事务中断。
需要调整的额外配置项
1. 修改MSDTC事务超时
- 打开「组件服务」→「计算机」→「我的电脑」→「分布式事务协调器」→「本地DTC」,右键选择属性
- 切换到「事务超时」选项卡,将超时时间改为3600秒(1小时)
- 重启MSDTC服务使设置生效
2. 调整IIS应用池参数
- 打开IIS管理器,找到目标应用池,右键选择「高级设置」
- 在「进程模型」下,将「闲置超时(分钟)」设置为大于37分钟的数值(比如40)
- 在「回收」下,将「固定时间间隔(分钟)」设为0(禁用定时回收)或远大于执行时间的数值
3. 优化EF Core连接复用(避免事务提升)
异步操作中频繁开关连接会导致事务被提升为分布式事务,可在TransactionScope内保持连接打开:
// 在TransactionScope内提前打开连接 await _dbContext.Database.OpenConnectionAsync(cancellationToken); try { // 执行所有存储过程逻辑 } finally { await _dbContext.Database.CloseConnectionAsync(); }
4. 检查SQL Server远程查询超时
如果存储过程涉及跨服务器查询,SQL Server默认远程查询超时为600秒(10分钟),需调整:
sp_configure 'remote query timeout', 2220; -- 37分钟=2220秒 RECONFIGURE;
额外建议
- 尽量避免长时间运行的事务,这类事务会占用数据库锁资源,影响其他业务。可考虑拆分存储过程为独立事务,或通过批量处理、异步队列优化执行时间。
- 排查单个存储过程的执行性能,看是否存在索引缺失、逻辑冗余等瓶颈,从根源缩短执行时长。
内容的提问来源于stack exchange,提问作者Odrai

