Entity Framework Core 生成的查询太慢

问题描述 投票:0回答:1

我们目前正在使用 .NET 8 和 EF Core 8。

我们用来计算结果并对其进行分页的查询遇到了时间问题。我们从一个需要 40 秒执行的查询开始,我们能够将 40 秒的时间减少到 4 秒,将 Azure SQL 弹性池中每个数据库的 e-DTU 从 5 个增加到 50 个

我们有一个事件系统,其中包含操作表、事件表(该表用于存储所有模块的事件)和声明(我们查询的表)。 Action 有 20 行,Event 有 640.338 行,Claim 有 62.000 行。

这就是表格的样子。

索赔和事件之间的关系是软的,因为事件表也用于其他表,它们使用列 ReferenceId 进行关联。

这就是 C# 代码的样子(工具生成此代码)

var query = Context.Set<ClaimEntity>().AsQueryable();
query = query.Where(x => x.Events.AsQueryable().OrderByDescending(e => e.CreatedOn).Select(e => e.Action.Code).FirstOrDefault() == "CREATE_CLAIM");
var total = query.Count();

这是它发送到 SQL Server 的查询

SELECT COUNT(*)
    FROM [dc].[Claim] AS [c]
    WHERE 
     (
          SELECT TOP(1) [a].[Code]
          FROM [dc].[Event] AS [e]
          INNER JOIN [dc].[Action] AS [a] ON [e].[ActionId] = [a].[Id]
          WHERE [c].[Id] = [e].[ReferenceId]
          ORDER BY [e].[CreatedOn] DESC
        ) = N'CREATE_CLAIM' 
        AND 
        [c].[DeletedOn] IS NULL

我们尝试使用 SSMS 数据库引擎优化顾问,它提供了此优化选项。

我们尝试了这个,但是执行时间还是4秒

CREATE NONCLUSTERED INDEX [_dta_index_Event_19_34099162__K3_K2_K10] ON [dc].[Event]
        (
            [ReferenceId] ASC,
            [ActionId] ASC,
            [CreatedOn] ASC
        )WITH (SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
sql-server entity-framework-core azure-sql-database database-tuning
1个回答
0
投票

这样的数据结构效率很低,因为您想根据与代码匹配的最新事件来计算索赔。正确的索引将有助于确保数据库能够尽快处理这些查询,但也会有限制,因为它确实需要“接触”很多值。

这是考虑反规范化的一个好例子,以引入存储在声明级别或作为最近事件的标志存储的“当前”代码。这两种解决方案都涉及创建“健康检查”,以定期查找并报告任何数据不一致的情况。然后可以更有效地对其进行索引。我还会对这样的当前代码使用数字枚举而不是字符串,这样索引更轻松。

例如,如果我们在 ClaimEntity 上添加 CurrentStatus:

public enum ClaimStatus
{
    None = 0,
    Created,
    //...
}

在 ClaimEntity 上添加/更新事件的行为可以封装在实体中,因此该方法是创建新事件的唯一方法,可以通过“ClaimEntity.AddEvent(code, ...)”之类的方法来完成。该方法验证所需的事件详细信息,创建事件并将其添加到 ClaimEntity.Events 集合中,并更新声明的当前状态。通过这种方式,获取“Created”事件的查询简化为:

var query = Context.Set<ClaimEntity>().AsQueryable();
query = query.Where(x => x.CurrentStatus == ClaimStatus.Created);
var total = query.Count();

健康检查可以是后台进程,也可以是数据库本身运行的进程,用于根据最新状态查询声明,查找任何不匹配的情况。如果发现任何问题,可以对其进行纠正,但应报告给开发团队,以追踪如何允许它们不同步。

另一种方法是向事件添加类似“IsCurrent”标志的内容,其中将事件添加到声明的行为会将任何/所有先前的 IsCurrent 标志设置为

False
,然后再添加 IsCurrent 设置为
 的新行True
。同样,这需要进行健康检查来搜索任何存在多个 IsCurrent == True 集或没有 IsCurrent == True 集的声明。 (存在事件的地方)这里的查询将简化为:

var query = Context.Set<ClaimEntity>().AsQueryable();
query = query.Where(x => x.Events.FirstOrDefault(e => e.IsCurrent).Code == "CREATE_CLAIM");
var total = query.Count();

这避免了需要对每个索赔的整个事件进行排序。同样,IsCurrent 标志需要适当索引。

© www.soinside.com 2019 - 2024. All rights reserved.