You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core设置高超时仍报Execution Timeout Expired错误排查

问题描述

我有一段结构非常简单的数据插入代码,实现如下:

private void Insert(IList<T> data)
{
    using (MyDbContext dbContext = MyDbContextFactory.CreateDbContext())
    {
        dbContext.AddRange(data);
        dbContext.SaveChanges();
    }
}

该代码大部分场景下可正常运行,但偶尔会执行失败——我仅单次插入50行数据,仅部分行包含体积稍大的BLOB字段,失败时抛出的异常堆栈信息如下:

Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.

System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out

at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\SqlInternalConnection.cs:line 820  
at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParser.cs:line 1572  
at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1070  
at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 2744  
at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync() in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 2258  
at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket() in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 2182  
at Microsoft.Data.SqlClient.TdsParserStateObject.TryPrepareBuffer() in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1222  
at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadByte(Byte& value) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParserStateObject.cs:line 1450  
at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2190  
at Microsoft.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParser.cs:line 2088  
at Microsoft.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\TdsParser.cs:line 9384  
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs:line 1311  
at Microsoft.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransaction(TransactionRequest transactionRequest, String name, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest) in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\SqlInternalConnectionTds.cs:line 1091  
at Microsoft.Data.SqlClient.SqlInternalTransaction.Commit() in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\sqlinternaltransaction.cs:line 344  
at Microsoft.Data.SqlClient.SqlTransaction.Commit() in E:\SqlClientInternal\agent-1\_work\5\s\src\Microsoft.Data.SqlClient\netfx\src\Microsoft\Data\SqlClient\SqlTransaction.cs:line 202  
at Microsoft.EntityFrameworkCore.Storage.RelationalTransaction.Commit()  
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)  
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IList`1 entries)  
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IList`1 entriesToSave)  
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(DbContext _, Boolean acceptAllChangesOnSuccess)  
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)  
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)  
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)  
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()

当前项目运行环境为.NET 4.8,使用Entity Framework Core 3.1.5版本;项目中同时引用了EFCore.BulkExtensions 3.1.1,该组件相关代码运行无异常。

为解决超时问题,我已按如下方式调高命令超时配置:

static class MyDbContextFactory
{
    public static MyDbContext CreateDbContext()
    {
        Action<SqlServerDbContextOptionsBuilder> setDbCommandTimeout = null;
        int? dbCommandTimeout = GlobalConfiguration.DbCommandTimeout;

        if (dbCommandTimeout.HasValue)
        {
            setDbCommandTimeout = (opts => opts.CommandTimeout(dbCommandTimeout.Value));
        }

        DbContextOptions<MyDbContext> options = new DbContextOptionsBuilder<MyDbContext>()
                                                            .UseSqlServer(GlobalConfiguration.GetConnectionString(), setDbCommandTimeout)
                                                            .Options;

        return new MyDbContext(options);
    }
}

项目中自定义DbContext的定义如下:

sealed class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<SharePointExportContext> options) : base(options)
    {
    }

    // [...]
}

但即使将超时参数配置为1200(即20分钟),操作依然会失败,部分场景下仅执行90秒左右就抛出超时错误。

核心疑问:该问题的可能诱因是什么?可以通过哪些方式调试定位?我个人怀疑该问题并非单纯的命令时长超限,可能是死锁类问题导致事务被强制取消。当前数据库服务端运行SQL Server 2016(版本号v13.0.4001.0),我已尝试删除并重建数据库,问题依然没有解决。


问题原因分析

首先看异常堆栈的关键信息:超时触发点在SqlInternalTransaction.Commit()也就是事务提交阶段,不是单条INSERT命令执行阶段,你配置的opts.CommandTimeout()仅控制EF Core生成的单条SQL命令的执行超时,完全不覆盖事务提交阶段的等待超时,这就是为什么你设了20分钟超时,还是会在90秒左右报错——事务提交阶段默认走连接层面的超时阈值,和你配置的命令超时不是一回事。

高概率诱因按优先级排序:

  • 锁等待/阻塞:插入带BLOB字段的数据时,SQL Server需要申请LOB数据页锁、索引行锁/页锁、表级意向锁,如果此时有其他未提交的长事务持有对应资源的排他锁,你的事务在提交阶段会一直等待锁释放,等待时间超过阈值就抛超时。注意这类问题如果没有形成循环等待(也就是死锁),SQL Server不会主动干预,不会抛1205死锁错误,只会等客户端超时断开。
  • BLOB写入触发的性能瓶颈:EF Core 3.1默认的批量插入逻辑对大尺寸BLOB字段的处理效率很低,会把BLOB内容作为参数拼到TDS包里,50行带大BLOB的数据可能生成几MB甚至更大的请求包,遇到网络波动、服务端内存吃紧时很容易出现传输/解析超时。
  • 服务端隐性等待:比如数据库日志文件设置为百分比自动增长,提交大事务时日志文件扩容需要申请磁盘空间、初始化文件,耗时超过阈值;或者表上建了效率很差的AFTER触发器,插入提交时触发器同步执行拖慢速度;或者磁盘IO打满,BLOB数据写入延迟过高。
  • 连接配置问题:比如连接字符串开启了MARS(多活动结果集)但使用不当,导致连接上的请求排队阻塞。
调试定位方案
  • 先抓阻塞链路:复现问题时直接在SSMS执行下面的SQL,查当前所有运行中请求的等待状态、阻塞会话ID:
SELECT 
    session_id,
    wait_type,
    wait_time/1000 AS wait_seconds,
    blocking_session_id,
    st.text AS exec_sql
FROM sys.dm_exec_requests r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) st
WHERE session_id > 50

如果查到blocking_session_id非0,顺着对应的会话ID查它的来源程序、执行的SQL、事务开启时长,就能直接定位锁的来源。

  • 捕获死锁/锁等待事件:开启SQL Server扩展事件会话,监控sqlserver.lock_wait_timeout、sqlserver.deadlock事件,能直接抓到资源争抢的完整链路,确认是不是锁导致的问题。
  • 做对照测试:临时把BLOB字段赋值为空再插入,如果不再报错,说明问题和BLOB写入直接相关,直接换项目里已经引用的EFCore.BulkExtensions做批量插入即可,它底层用SqlBulkCopy机制,写入BLOB的效率比EF Core默认逻辑高几个量级,还能避开大参数的性能问题。
  • 排查服务端配置:检查数据库数据文件、日志文件的自动增长设置,不要用百分比增长,改成固定步长(比如日志每次增长1GB),关闭日志文件的即时初始化;检查表上有没有未优化的触发器、过多的索引;复现问题时监控服务端磁盘延迟、CPU、内存使用率,排除硬件资源瓶颈。
  • 验证超时配置:可以在连接字符串里显式添加Connection Timeout=1200参数,或者在创建事务时手动指定事务超时,确认事务提交阶段的超时是否被正确覆盖。

内容的提问来源于stack exchange,提问作者TravelingFox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:03:25