EF Core多JOIN查询性能极差,同语句在SSMS执行极快
问题场景
我有一个包含多个导航属性的实体,需要加载其中三个属性。最初编写的LINQ查询如下:
await _context.EventParticipant .Include(x => x.Employee) .Include(x => x.ExcuseDescription) .Include(x => x.RegistrationMethod) .Where(x => x.EventId == eventId) .ToListAsync();
该查询在获取约300行数据时出现严重性能问题,几乎无法完成。查看LINQ生成的SQL语句,在SSMS中执行耗时不到1秒。添加AsNoTracking()后性能无任何改善,调试显示查询执行+实体物化耗时约16秒。
尝试仅选择所需列,编写投影查询后性能提升显著,相同300行数据下耗时不到2秒:
await _context.EventParticipant .Where(x => x.EventId == eventId).AsNoTracking() .Select(participant => new EventParticipantViewModel() { EventId = eventId, EmployeeNumber = participant.EmployeeNumber, Department = participant.Employee.DepartmentName, FirstName = participant.Employee.Firstname, LastName = participant.Employee.Lastname, IsInvited = participant.IsInvited, ExcuseDescription = participant.ExcuseDescription.Description ?? null, RegisterMethod = participant.RegistrationMethod.Description ?? null, }) .ToListAsync();
虽然两个查询生成的SQL差异极小,但性能差距巨大,以下是具体原因:
核心差异分析
对象创建与数据量开销:
使用Include时,EF Core会创建完整的EventParticipant实体及所有关联的Employee、ExcuseDescription、RegistrationMethod实体,每个实体包含其所有字段(即使你不需要)。这意味着要创建300个EventParticipant+ 300个关联实体(共约1200个对象),且每个对象要填充大量无关属性。而投影查询仅创建300个EventParticipantViewModel,只填充你需要的几个属性,对象数量和数据填充量都大幅减少。物化流程复杂度:
对于Include查询,EF Core需要处理多表Join后的结果集,将不同表的列映射到对应的实体对象,还要维护导航属性的引用关系(即使AsNoTracking,也需要解析结果集并拼接成完整的对象图)。这个过程涉及结果分组、实体匹配等额外逻辑,耗时较高。而投影查询是直接将SQL返回的列映射到ViewModel的属性,逻辑简单直接,不需要处理实体间的关联关系。内存处理与GC压力:
大量完整实体的创建会占用更多内存,同时后续GC回收这些对象的开销也更大。投影查询的ViewModel体积小,内存占用低,GC压力也小,整体运行效率更高。
内容的提问来源于stack exchange,提问作者mobearette

