You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 8 + EF Core 8分页计数查询性能优化求助

优化Azure SQL中EF Core生成的Claim计数查询性能

当前场景与问题

我们基于.NET 8 + EF Core 8开发,分页前的Claim计数查询存在性能瓶颈:初始查询耗时40秒,将Azure SQL弹性池的e-DTUs从5提升至50后,耗时降至4秒,但仍未达到预期。

表结构信息

  • Action表:20行
  • Event表:640,338行(服务多模块,通过ReferenceId与Claim软关联)
  • Claim表:62,000行

现有代码与SQL

EF Core生成的查询代码:

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语句:

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]

优化方案

1. 重构查询逻辑,消除相关子查询

当前SQL对每个Claim执行一次子查询获取最新Event的ActionCode,属于逐行触发的相关子查询,效率极低。改用JOIN+窗口函数先筛选每个ReferenceId的最新Event,再关联统计:

优化后的SQL:

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

对应的EF Core代码调整:

var latestEventQuery = Context.Set<EventEntity>()
    .Join(Context.Set<ActionEntity>(), 
          e => e.ActionId, 
          a => a.Id, 
          (e, a) => new { e.ReferenceId, a.Code, e.CreatedOn })
    .GroupBy(x => x.ReferenceId)
    .Select(g => g.OrderByDescending(x => x.CreatedOn).FirstOrDefault())
    .Where(x => x.Code == "CREATE_CLAIM");

var total = Context.Set<ClaimEntity>()
    .Where(c => c.DeletedOn == null)
    .Join(latestEventQuery, 
          c => c.Id, 
          le => le.ReferenceId, 
          (c, le) => c)
    .Count();

2. 优化Event表的覆盖索引

现有索引的字段顺序和包含字段不合理,建议创建覆盖索引,让数据库无需回表查询:

CREATE NONCLUSTERED INDEX IX_Event_ReferenceId_CreatedOn
ON [dc].[Event] (ReferenceId ASC, CreatedOn DESC)
INCLUDE (ActionId); -- 包含关联Action所需的字段,避免键查找

3. 给Claim表添加过滤索引

如果DeletedOn IS NULL是高频过滤条件,创建过滤索引加速基础查询:

CREATE NONCLUSTERED INDEX IX_Claim_DeletedOn
ON [dc].[Claim] (Id)
WHERE DeletedOn IS NULL;

4. 预计算缓存(可选)

若计数查询的实时性要求不高,可定期预计算结果并缓存(如Redis或数据库缓存表),比如每分钟计算一次符合条件的Claim数量,直接读取缓存值彻底解决性能问题。

5. 分析查询执行计划

在SSMS中执行原查询,查看实际执行计划,重点检查:

  • 是否存在表扫描/聚集索引扫描
  • 是否有高开销的键查找操作
  • 排序步骤的资源消耗
    根据执行计划提示针对性调整索引或查询逻辑。

内容的提问来源于stack exchange,提问作者Marlon Adarme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 23:47:36