繁体   English   中英

在具有多个 JOIN 的 EF Core 中执行极慢的查询,而在 SSMS 中却快速执行完全相同的查询

[英]Extremely slow query execution in EF Core with several JOINs while exactly the same query is executing fast in 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 生成的查询,它是这样的:

SELECT 
    [e].[EmployeeNumber], [e].[EventId], [e].[ExcuseDescriptionId], 
    [e].[IsInvited], [e].[RegistrationMethodId], 
    [v].[employee_number], [v].[company_email], 
    [v].[department_name], [v].[employee_email], 
    [v].[employeecard_rfid], [v].[firstname], [v].[lastname], 
    [v].[status], [v].[mandant], [v].[upn], [v].[userid], 
    [e0].[Id], [e0].[Description], [r].[Id], [r].[Description]
FROM 
    [EventParticipant] AS [e]
INNER JOIN 
    [V_Employee] AS [v] ON [e].[EmployeeNumber] = [v].[employee_number]
LEFT JOIN 
    [ExcuseDescription] AS [e0] ON [e].[ExcuseDescriptionId] = [e0].[Id]
LEFT JOIN 
    [RegistrationMethod] AS [r] ON [e].[RegistrationMethodId] = [r].[Id]
WHERE 
    [e].[EventId] = @__eventId_0

现在,这个查询在 SSMS 中运行不到一秒钟,所以我的下一个猜测是问题是由实体跟踪引起的,但是在实体化实体之前添加AsNoTracking()并没有带来任何改进。

有趣的是,调试 output 将显示查询在大约 16 秒后执行(这仍然非常慢),但实体仍然永远不会实现。 该查询确实加载了一些额外的列,所以我尝试的下一件事是 select 仅我需要的列,所以我最终得到以下内容:

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();

现在,这生成了以下查询:

SELECT @__eventId_0 AS [EventId], [e].[EmployeeNumber], [v].[department_name] AS [Department], [v].[firstname] AS [FirstName], [v].[lastname] AS [LastName], [e].[IsInvited], COALESCE([e0].[Description], NULL) AS [ExcuseDescription], COALESCE([r].[Description], NULL) AS [RegisterMethod]
  FROM [EventParticipant] AS [e]
  INNER JOIN [V_Employee] AS [v] ON [e].[EmployeeNumber] = [v].[employee_number]
  LEFT JOIN [ExcuseDescription] AS [e0] ON [e].[ExcuseDescriptionId] = [e0].[Id]
  LEFT JOIN [RegistrationMethod] AS [r] ON [e].[RegistrationMethodId] = [r].[Id]
  WHERE [e].[EventId] = @__eventId_0

尽管查询本身与原始查询没有太大区别,但对性能的影响是巨大的:上面的查询在不到 2 秒的时间内以相同的 300 行来执行,实体实现!

谁能解释一下为什么会这样? EF Core 在后台做了什么样的魔法会影响这样的性能?

正如@Stu 提到的,很难确定不知道所涉及的索引或所涉及的子集合记录的数量,但要意识到这一点。Include 本质上是执行 select * (使用子表中的所有列),这可能会拉出不同的索引比您的自定义投影。 自定义投影可能正在使用覆盖索引,从而提高您的性能。

您没有提到的另一件事是您使用的是哪个版本的 EF(我假设是核心)。 某些版本从相同的 LINQ 表达式生成了截然不同的查询,其中一些可能导致更好或更差的性能。 例如,早期版本的 EF 核心可能会将此查询拆分为多个延迟加载的表达式,或者在子表 Id 上注入 order by,如果不包含在索引中,可能会导致性能问题(尤其是具有唯一标识符 Id)。

在这两个查询中,大部分时间都花在等待您的链接服务器上。 分布式查询的计划更改可能非常痛苦。 因此,虽然您还没有捕获任何真正长时间运行的执行的实际执行计划,但它可能是由您的链接服务器引起的。

        <WaitStats>
          <Wait WaitType="OLEDB" WaitTimeMs="939" WaitCount="16" />
        </WaitStats>

首先,您真的必须使用链接服务器吗? 你不能把数据复制到本地数据库吗?

如果您确实使用链接服务器,请尽量不要将远程表与本地表连接起来。 使用链接服务器中的数据加载临时表,然后将其加入本地表会更可靠。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM