简体   繁体   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

I have an entity containing several navigation properties and I need to load three of them.我有一个包含多个导航属性的实体,我需要加载其中的三个。 The initial Linq query I wrote is:我写的最初的 Linq 查询是:

await _context.EventParticipant
              .Include(x => x.Employee)
              .Include(x => x.ExcuseDescription)
              .Include(x => x.RegistrationMethod)
              .Where(x => x.EventId == eventId)
              .ToListAsync();

This led to massive performance issues, causing the query to seemingly never finish when there were about 300 rows to fetch.这导致了大量的性能问题,导致查询似乎永远无法完成,当有大约 300 行要获取时。 The first thing I took a look at was the query generated by LINQ which was this:我首先看的是 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

Now, this query runs in SSMS in less than a second, so my next guess was that the problem is caused by entity tracking, but adding AsNoTracking() before materializing the entities led to no improvements.现在,这个查询在 SSMS 中运行不到一秒钟,所以我的下一个猜测是问题是由实体跟踪引起的,但是在实体化实体之前添加AsNoTracking()并没有带来任何改进。

Interestingly enough, the debug output would show that the query was executed after about 16 seconds (which is still extremely slow), but the entities would still never materialize.有趣的是,调试 output 将显示查询在大约 16 秒后执行(这仍然非常慢),但实体仍然永远不会实现。 The query does load a few extra columns so the next thing I tried was to select only the columns that I need, so I ended up with the following:该查询确实加载了一些额外的列,所以我尝试的下一件事是 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();

Now, this generated the following query:现在,这生成了以下查询:

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

Although the query itself is not much different than the original one, the performance impact is immense: the query above executes and entities materialize in less than 2 seconds with the same 300-ish rows!尽管查询本身与原始查询没有太大区别,但对性能的影响是巨大的:上面的查询在不到 2 秒的时间内以相同的 300 行来执行,实体实现!

Could anyone please explain why is this happening?谁能解释一下为什么会这样? What kind of magic does the EF Core do in the background that would impact the performance like this? EF Core 在后台做了什么样的魔法会影响这样的性能?

As @Stu mentioned, it's hard to say for sure not knowing the indexes involved or the amount of child collection records involved, but realize that.Include essentially does a select * (using all columns from the child table) which likely is pulling a different index than your custom projection.正如@Stu 提到的,很难确定不知道所涉及的索引或所涉及的子集合记录的数量,但要意识到这一点。Include 本质上是执行 select * (使用子表中的所有列),这可能会拉出不同的索引比您的自定义投影。 The custom projection may be using a covering index and thus increasing your performance.自定义投影可能正在使用覆盖索引,从而提高您的性能。

The other thing you didn't mention is which version of EF (core I'm assuming) you are using.您没有提到的另一件事是您使用的是哪个版本的 EF(我假设是核心)。 Some versions have generated drastically different queries from the same LINQ expression and some of them could result in better or worse performance.某些版本从相同的 LINQ 表达式生成了截然不同的查询,其中一些可能导致更好或更差的性能。 For example, earlier versions of EF core might split this query into multiple lazy loaded expressions or injects order by on the child table Ids which if not included in the indexes could lead to performance issues (particularly with unique identifier Ids).例如,早期版本的 EF 核心可能会将此查询拆分为多个延迟加载的表达式,或者在子表 Id 上注入 order by,如果不包含在索引中,可能会导致性能问题(尤其是具有唯一标识符 Id)。

In both queries most of the time is spent waiting for your linked server.在这两个查询中,大部分时间都花在等待您的链接服务器上。 And plan changes for distributed queries can be very painful.分布式查询的计划更改可能非常痛苦。 So while you haven't captured the actual execution plan for any really long-running execution, it's probably caused by your linked server.因此,虽然您还没有捕获任何真正长时间运行的执行的实际执行计划,但它可能是由您的链接服务器引起的。

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

First, do you really have to use a linked server?首先,您真的必须使用链接服务器吗? Can't you just copy the data to the local database?你不能把数据复制到本地数据库吗?

If you do use linked server, try to never join remote tables with local tables.如果您确实使用链接服务器,请尽量不要将远程表与本地表连接起来。 You it's more reliable to load a temp table with data from your linked server and then join that to your local tables.使用链接服务器中的数据加载临时表,然后将其加入本地表会更可靠。

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

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