ASP.NET+EF关联查询部署后因数据量增大触发API超时故障排查
如何解决MariaDB数据累积导致的API查询超时问题?
初始阶段API运行正常,但随着MariaDB数据库中数据不断累积,活动参与者无法获取其研习班数据(如签到记录、历史场次),API请求因超时失败。部署一段时间后,管理员仪表盘API也出现同样的超时问题。
参与者API原代码
[HttpGet] [Authorize(Roles = "Admin,Participant")] [ValidateReferrerAttribute] public async Task<ParticipantSessionDto> GetSessions() { if (User.Identity is { IsAuthenticated: true }) { var configuration = new MapperConfiguration( cfg => cfg.CreateProjection<Participant, ParticipantSessionDto>()); return await _entities .AsNoTracking() .Include(x => x.CheckIns) .ThenInclude(x => x.Session) .Include(x => x.Events) .Include(x => x.Sessions) .ThenInclude(x => x.Event) .Include(x => x.Sessions) .ThenInclude(x => x.Speakers) .ThenInclude(x => x.Avatar) .Include(x => x.Sessions) .ThenInclude(x => x.Materials) .ProjectTo<ParticipantSessionDto>(configuration) .FirstAsync(x => x.UserName == User.Identity.Name); } return null; }
超时异常日志
MySqlConnector.MySqlException (0x80004005): The Command Timeout expired before the operation completed. ---> System.Net.Sockets.SocketException (125): Operation canceled at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource<System.Int32>.GetResult(Int16 token) at MySqlConnector.Protocol.Serialization.SocketByteHandler.DoReadBytesAsync(Memory`1 buffer) in /_/src/MySqlConnector/Protocol/Serialization/SocketByteHandler.cs:line 109 at MySqlConnector.Protocol.Serialization.SocketByteHandler.DoReadBytesAsync(Memory`1 buffer) in /_/src/MySqlConnector/Protocol/Serialization/SocketByteHandler.cs:line 109 at MySqlConnector.Protocol.Serialization.BufferedByteReader.ReadBytesAsync(IByteHandler byteHandler, ArraySegment`1 buffer, Int32 totalBytesToRead, IOBehavior ioBehavior) in /_/src/MySqlConnector/Protocol/Serialization/BufferedByteReader.cs:line 34 at MySqlConnector.Protocol.Serialization.ProtocolUtility.<ReadPacketAfterHeader>g__AddContinuation|2_0(ValueTask`1 payloadBytesTask, Int32 payloadLength, ProtocolErrorBehavior protocolErrorBehavior, Exception packetOutOfOrderException) in /_/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs:line 434 at MySqlConnector.Protocol.Serialization.ProtocolUtility.<DoReadPayloadAsync>g__AddContinuation|5_0(ValueTask`1 readPacketTask, BufferedByteReader bufferedByteReader, IByteHandler byteHandler, Func`1 getNextSequenceNumber, ArraySegmentHolder`1 previousPayloads, ProtocolErrorBehavior protocolErrorBehavior, IOBehavior ioBehavior) in /_/src/MySqlConnector/Protocol/Serialization/ProtocolUtility.cs:line 480 at MySqlConnector.Core.ServerSession.ReceiveReplyAsyncAwaited(ValueTask`1 task) in /_/src/MySqlConnector/Core/ServerSession.cs:line 956 at MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior) in /_/src/MySqlConnector/Core/ResultSet.cs:line 133 at MySqlConnector.MySqlDataReader.ActivateResultSet(CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 108 at MySqlConnector.MySqlDataReader.CreateAsync(CommandListPosition commandListPosition, ICommandPayloadCreator payloadCreator, IDictionary`2 cachedProcedures, IMySqlCommand command, CommandBehavior behavior, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlDataReader.cs:line 456 at MySqlConnector.Core.CommandExecutor.ExecuteReaderAsync(IReadOnlyList`1 commands, ICommandPayloadCreator payloadCreator, CommandBehavior behavior, Activity activity, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/Core/CommandExecutor.cs:line 56 at MySqlConnector.MySqlCommand.ExecuteReaderAsync(CommandBehavior behavior, IOBehavior ioBehavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlCommand.cs:line 331 at MySqlConnector.MySqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) in /_/src/MySqlConnector/MySqlCommand.cs:line 323 at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken) at Pomelo.EntityFrameworkCore.MySql.Storage.Internal.MySqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken) at EventManager.Server.Controllers.ParticipantController.GetSessions() in C:\Users\CMSB\Desktop\EventManager\EventManager.Server\Controllers\ParticipantController.cs:line 173
仪表盘API原代码
[HttpGet] [Authorize(Roles = "Admin")] [ValidateReferrerAttribute] public async Task<Event> GetForDashboard(Guid id) { if (await _entities.AnyAsync(x => x.EventId == id)) { var data = await _entities .AsNoTracking() .Include(x => x.CheckIns) .ThenInclude(x => x.Participant) .Include(x => x.Participants) .ThenInclude(x => x.Feedbacks) .Include(x => x.Sessions) .ThenInclude(x => x.Speakers) .Include(x => x.Sessions) .ThenInclude(x => x.Participants) .Include(x => x.Sessions) .ThenInclude(x => x.CheckIns) .ThenInclude(x => x.Participant) .FirstAsync(x => x.EventId == id); return data; } return null; }
解决方案
1. 优化EF Core查询逻辑
- 提前过滤数据:将
FirstAsync(x => x.UserName == ...)的过滤条件提前到Where子句中,让数据库先筛选出目标用户的数据,再加载关联表,避免拉取全表数据后再过滤。 - 避免过度Include:检查DTO或返回实体实际需要的字段,只加载必要的关联数据。比如如果
ParticipantSessionDto不需要Speaker的Avatar,就去掉对应的ThenInclude(x => x.Avatar),减少关联查询的复杂度。 - 复用Mapper配置:不要在每次请求时创建新的
MapperConfiguration,将其注册为全局单例(比如在Program.cs中配置AutoMapper),减少重复初始化的开销。 - 减少数据库请求次数:仪表盘API中先用
AnyAsync再用FirstAsync会导致两次数据库查询,直接改用FirstOrDefaultAsync即可完成一次查询。
优化后的参与者API示例
[HttpGet] [Authorize(Roles = "Admin,Participant")] [ValidateReferrerAttribute] public async Task<ParticipantSessionDto> GetSessions() { if (User.Identity is { IsAuthenticated: true }) { var userName = User.Identity.Name; // 复用全局AutoMapper配置 return await _entities .AsNoTracking() .Where(x => x.UserName == userName) // 提前过滤数据 .Include(x => x.CheckIns) .ThenInclude(x => x.Session) .Include(x => x.Events) .Include(x => x.Sessions) .ThenInclude(x => x.Event) // 只保留DTO需要的关联 .Include(x => x.Sessions) .ThenInclude(x => x.Speakers) .Include(x => x.Sessions) .ThenInclude(x => x.Materials) .ProjectTo<ParticipantSessionDto>(_mapper.ConfigurationProvider) .FirstAsync(); } return null; }
优化后的仪表盘API示例
[HttpGet] [Authorize(Roles = "Admin")] [ValidateReferrerAttribute] public async Task<Event> GetForDashboard(Guid id) { // 一次查询完成判断与数据获取 return await _entities .AsNoTracking() .Where(x => x.EventId == id) .Include(x => x.CheckIns) .ThenInclude(x => x.Participant) .Include(x => x.Participants) .ThenInclude(x => x.Feedbacks) .Include(x => x.Sessions) .ThenInclude(x => x.Speakers) .Include(x => x.Sessions) .ThenInclude(x => x.Participants) .Include(x => x.Sessions) .ThenInclude(x => x.CheckIns) .ThenInclude(x => x.Participant) .FirstOrDefaultAsync(); }
2. 数据库层面优化
- 添加索引:给查询过滤字段和外键字段添加索引,例如:
Participant表的UserName字段Event表的EventId字段- 关联表的外键(如
CheckIns.ParticipantId、Sessions.EventId等)
索引可以大幅提升查询和关联的速度,避免全表扫描。
- 分析执行计划:使用MariaDB的
EXPLAIN命令分析EF Core生成的SQL语句,定位全表扫描、关联效率低的环节,针对性优化。 - 调整数据库超时配置:临时增大查询超时时间(比如在DbContext配置中设置
CommandTimeout(60)),但这只是临时方案,核心还是优化查询本身。
3. 拆分复杂查询
如果关联表过多,一次查询拉取的数据量过大,可以将大查询拆分为多个小查询,分别获取主实体和关联数据,再在内存中组装结果。例如先查Participant,再单独查其CheckIns、Sessions,最后合并到DTO中,避免一次生成复杂的多表关联SQL。
内容的提问来源于stack exchange,提问作者Ibrahim Timimi
相关产品推荐
相关产品推荐

