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

LINQ查询性能远逊于等价SQL,求优化方案

LINQ查询性能优化问题

我写了一段LINQ查询,用SQL Profiler抓取其生成的T-SQL并查看统计信息后,发现性能极差,执行计划复杂到难以解读。但等价的原生SQL查询在读取性能上要好几个数量级。

想问下有没有办法优化该LINQ查询的性能?还是只能接受现状改用存储过程?

补充说明:输入包含235个ID,SQL中用子查询获取ID仅为操作便利,实际所用ID与LINQ中的完全一致。


LINQ代码

var itemchangeList = (from itm in db.AS_ITM
                      join rsi in db.AS_ITM_RTL_STR on itm.ID_ITM equals rsi.ID_ITM
                      join sti in db.AS_ITM_STK on itm.ID_ITM equals sti.ID_ITM
                      join pi in db.ID_IDN_PS on itm.ID_ITM equals pi.ID_ITM
                      join ics in db.AS_ITM_CustomFilters on itm.ID_ITM equals ics.ItemId into icsGroup
                      from ics in icsGroup.DefaultIfEmpty()  // 左连接
                      join cs in db.CustomFilters on ics.CustomFilterId equals cs.CustomFilterId into csGroup
                      from cs in csGroup.DefaultIfEmpty()    // 左连接
                      where rsi.ID_CPY == WsKey.CompanyID && rsi.ID_STR_RT == WsKey.StoreID && itemIds.Contains(itm.ID_ITM)
                      group new { itm, sti, pi, cs } by itm.ID_ITM into grouped
                      select new Contexts.Items.PendingItemChange
                      {
                          ItemId = grouped.Key,
                          AuthForSale = grouped.FirstOrDefault().itm.FL_AZN_FR_SLS == "1" ? true : false,
                          SaleType = null,
                          StoreManaged = grouped.FirstOrDefault().itm.StoreManaged,
                          ApacsCode = null,
                          ProhibitEmployeeDiscount = grouped.FirstOrDefault().pi.FL_DSC_EM_ALW == "1" ? true : false,
                          ProhibitPriceModification = grouped.FirstOrDefault().itm.FL_ITM_PRC_MOD == "1" ? true : false,
                          RequiredMargin = grouped.FirstOrDefault().sti.Margin,
                          ProductTagsList = grouped.Select(x => x.cs.CustomFilterId.ToString()).Distinct().ToList(),
                          EligibleForOnlineDelivery = grouped.FirstOrDefault().sti.EligibleForOnlineDelivery,
                          DepositItemId = grouped.FirstOrDefault().pi.ID_ITM_DS,
                          Analgesic = grouped.FirstOrDefault().itm.Analgesic == "1" ? true : false,
                          CountryOfOrigin = grouped.FirstOrDefault().sti.CountryOfOrigin,
                          Brand = grouped.FirstOrDefault().sti.Brand,
                          MiscInfo = grouped.FirstOrDefault().sti.MiscText
                      }).ToList();

等价原生SQL代码

SELECT
    itm.ID_ITM AS ItemId,
    CASE WHEN MAX(CAST(itm.FL_AZN_FR_SLS AS INT)) = 1 THEN 'true' ELSE 'false' END AS AuthForSale,
    CASE WHEN MAX(CAST(itm.StoreManaged AS INT)) = 1 THEN 'true' ELSE 'false' END AS StoreManaged,
    CASE WHEN MAX(CAST(pi.FL_DSC_EM_ALW AS INT)) = 1 THEN 'true' ELSE 'false' END AS ProhibitEmployeeDiscount,
    CASE WHEN MAX(CAST(itm.FL_ITM_PRC_MOD AS INT)) = 1 THEN 'true' ELSE 'false' END AS ProhibitPriceModification,
    MAX(sti.Margin) AS RequiredMargin,
    CASE WHEN MAX(CAST(sti.EligibleForOnlineDelivery AS INT)) = 1 THEN 'true' ELSE 'false' END AS EligibleForOnlineDelivery,
    MAX(pi.ID_ITM_DS) AS DepositItemId,
    CASE WHEN MAX(CAST(itm.Analgesic AS INT)) = 1 THEN 'true' ELSE 'false' END AS Analgesic,
    MAX(sti.CountryOfOrigin) AS CountryOfOrigin,
    MAX(sti.Brand) AS Brand,
    MAX(sti.MiscText) AS MiscInfo,
    STRING_AGG(cs.CustomFilterId, ',') AS ProductTagsList
FROM AS_ITM itm
JOIN AS_ITM_RTL_STR rsi ON itm.ID_ITM = rsi.ID_ITM
JOIN AS_ITM_STK sti ON itm.ID_ITM = sti.ID_ITM
JOIN ID_IDN_PS pi ON itm.ID_ITM = pi.ID_ITM
LEFT JOIN AS_ITM_CustomFilters ics ON itm.ID_ITM = ics.ItemId
LEFT JOIN CustomFilters cs ON ics.CustomFilterId = cs.CustomFilterId
WHERE itm.ID_ITM IN (SELECT ItemId FROM PendingItemChanges)
GROUP BY itm.ID_ITM;

性能统计

  • LINQ查询性能统计:
    Linq性能统计
  • 原生SQL查询性能统计:
    SQL性能统计

优化方案

1. 替换FirstOrDefault()为聚合函数,对齐原生SQL逻辑

LINQ中分组后用grouped.FirstOrDefault()获取字段,会导致EF生成低效的嵌套查询——因为它无法确认同一分组内这些字段值唯一。而原生SQL用MAX()聚合(同一ID_ITM下值唯一,聚合结果与FirstOrDefault()一致),可以让EF生成更高效的SQL:

// 示例:替换AuthForSale的获取方式
AuthForSale = grouped.Max(x => x.itm.FL_AZN_FR_SLS) == "1" ? true : false,
// 其他字段同理修改
RequiredMargin = grouped.Max(x => x.sti.Margin),
DepositItemId = grouped.Max(x => x.pi.ID_ITM_DS),
CountryOfOrigin = grouped.Max(x => x.sti.CountryOfOrigin),
// ... 其余字段均用Max/Min聚合

2. 优化标签列表生成,在数据库端完成聚合

LINQ中Select().Distinct().ToList()会把所有标签数据拉取到内存再处理,而原生SQL用STRING_AGG在数据库端完成聚合。可使用EF的内置函数实现:

// EF Core 2.1+ 可用EF.Functions.StringAgg
ProductTagsList = grouped
    .Where(x => x.cs != null)
    .Select(x => x.cs.CustomFilterId.ToString())
    .Aggregate((a, b) => $"{a},{b}")

3. 提前过滤数据,减少关联数据量

将itemIds.Contains(itm.ID_ITM)和门店、公司ID的过滤条件提前到关联前,让数据库更早排除无用数据:

var filteredRsi = db.AS_ITM_RTL_STR.Where(r => r.ID_CPY == WsKey.CompanyID && r.ID_STR_RT == WsKey.StoreID);
var itemchangeList = (from itm in db.AS_ITM.Where(i => itemIds.Contains(i.ID_ITM))
                      join rsi in filteredRsi on itm.ID_ITM equals rsi.ID_ITM
                      // ... 其余关联逻辑不变

4. 关闭实体跟踪,减少不必要开销

因为仅需读取数据,无需跟踪实体状态,添加AsNoTracking()可降低EF的内存和性能消耗:

var itemchangeList = (from itm in db.AS_ITM.AsNoTracking()
                      // ... 其余代码不变
                      ).ToList();

5. 若以上优化无效,直接使用原生SQL或存储过程

如果调整LINQ后性能仍未达标,可直接在EF中执行原生SQL:

var itemchangeList = db.PendingItemChange.FromSqlRaw(@"
    SELECT
        itm.ID_ITM AS ItemId,
        CASE WHEN MAX(CAST(itm.FL_AZN_FR_SLS AS INT)) = 1 THEN 'true' ELSE 'false' END AS AuthForSale,
        -- ... 其余原生SQL内容
        WHERE itm.ID_ITM IN ({0})
", string.Join(",", itemIds)).ToList();

或创建存储过程,通过EF调用,完全复用原生SQL的高效执行计划。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 20:59:56