【问题标题】:Linq to Entity Query takes more than 2 minutes to executeLinq to Entity Query 执行时间超过 2 分钟
【发布时间】:2023-03-12 15:44:01
【问题描述】:

我正在尝试使用 LINQ to Entity 执行以下查询,但遇到了性能问题。结果返回大约需要 2-3 分钟。我需要关于如何提高这些查询的性能的建议

这是我用来计算特定日期之间订单总数的方法。数据在此处分为两个表。我需要从一个表中获取交易编号,然后使用这些交易提取订单并将它们汇总,但是对于某些条件来说查询太慢了,因为我在第一个查询中返回了大约 10k+ 条记录。

public int GetOrderQuantity(List<int> TransactionNumbers, DateTime FromDate, DateTime ToDate)
{
    List<int> transactions = _context.RP_PART_TRANSACTIONS.Where(trd => TransactionNumbers.Contains(trd.TRANSACTION_NUMBER) && trd.TRANSACTION_DATE >= FromDate && trd.TRANSACTION_DATE <= ToDate).Select(tr => tr.TRANSACTION_NUMBER).ToList();

    return _context.RP_PART_TRANSACTION_DETAILS.Where(trd => transactions.Contains(trd.TRANSACTION_NUMBER)).Select(tr => tr.PART_QTY).ToList().Sum();
}

【问题讨论】:

  • 您是否尝试使用IQueryable&lt;int&gt; transactions = .... .Select(tr =&gt; tr.TRANSACTION_NUMBER)?没有 ToList() 方法,防止数据库服务器重复选择。
  • 如果执行两次查询,第二次执行是否很快?
  • 您真的需要 2 个查询吗? TransactionNumbers 列表中大约有多少项目?
  • @VMA 现在会试试这个
  • @G1P 这就是我的意思,这需要很长时间,因为您的 L2E 是生成的,而不是因为查询本身。 TransactionNumbers 中有多少项?我怀疑这是你的罪魁祸首

标签: c# entity-framework linq oracle11g


【解决方案1】:

这段代码至少有两个问题

List<int> transactions = _context.RP_PART_TRANSACTIONS.Where(trd => TransactionNumbers.Contains(trd.TRANSACTION_NUMBER) && trd.TRANSACTION_DATE >= FromDate && trd.TRANSACTION_DATE <= ToDate).Select(tr => tr.TRANSACTION_NUMBER).ToList();

return _context.RP_PART_TRANSACTION_DETAILS.Where(trd => transactions.Contains(trd.TRANSACTION_NUMBER)).Select(tr => tr.PART_QTY).ToList().Sum();

首先,您使用两个查询,这两个查询可以使用join 运算符合并为一个。考虑到第一个查询可以返回很多记录(如您提到的 10K+),并且在数据库端无法有效处理查询中的内存中 Contains,这可能会给您很大的进步。

其次,在后面的查询中首先使用ToList(),然后是Sum,这需要从数据库中读取所有记录并将它们汇总到内存中。让数据库做总和会非常有效。

话虽如此,还是值得尝试以下方法

var result =
    (from td in _context.RP_PART_TRANSACTION_DETAILS
     join t in _context.RP_PART_TRANSACTIONS
         on td.TRANSACTION_NUMBER equals t.TRANSACTION_NUMBER
     where TransactionNumbers.Contains(t.TRANSACTION_NUMBER)
         && t.TRANSACTION_DATE >= FromDate && t.TRANSACTION_DATE <= ToDate
     select td.PART_QTY)
    .Sum();

更新:从 cmets 注意到您的 TransactionNumbers 包含约 16K 项。 EF 会将TransactionNumbers.Contains(t.TRANSACTION_NUMBER) 部分转换为SQL t.TRANSACTION_NUMBER IN (...) 子句,在IN 子句中列出16K 个数字,这将导致Oracle CBO 选择全表扫描而不是索引扫描。您可以尝试通过像这样包含列表的下限和上限来强制索引范围扫描

var minNumber = TransactionNumbers.Min();
var maxNumber = TransactionNumbers.Max();
var result =
    (from td in _context.RP_PART_TRANSACTION_DETAILS
     join t in _context.RP_PART_TRANSACTIONS
         on td.TRANSACTION_NUMBER equals t.TRANSACTION_NUMBER
     where t.TRANSACTION_NUMBER >= minNumber && t.TRANSACTION_NUMBER <= maxNumber
         && TransactionNumbers.Contains(t.TRANSACTION_NUMBER)
         && t.TRANSACTION_DATE >= FromDate && t.TRANSACTION_DATE <= ToDate
     select td.PART_QTY)
    .Sum();

如果仍然很慢,我能想到的最后一件事是尝试在数据库中进行尽可能多的过滤/聚合(w/o TransactionNumbers 过滤器),然后在内存中进行最终的过滤/聚合,就像这样

var query =
    from td in _context.RP_PART_TRANSACTION_DETAILS
    join t in _context.RP_PART_TRANSACTIONS
        on td.TRANSACTION_NUMBER equals t.TRANSACTION_NUMBER
    where t.TRANSACTION_DATE >= FromDate && t.TRANSACTION_DATE <= ToDate
    group td by t.TRANSACTION_NUMBER into g
    select new { TRANSACTION_NUMBER = g.Key, PART_QTY = g.Sum(td => td.PART_QTY) };

var filter = new HashSet<int>(TransactionNumbers); // For efficient lookup

var result = query.AsEnumerable() // Important! Switch to in memory context
    .Where(td => filter.Contains(td.TRANSACTION_NUMBER))
    .Sum(td => td.PART_QTY);

【讨论】:

  • 嗨,Ivan,刚刚尝试了你上面提到的查询,它比以前花费了更多时间。从邮递员那里拿走了 181121 毫秒
  • 你看生成的SQL了吗?它高度依赖于正确的数据库索引和TransactionNumbers 列表中的项目数(请参阅我的评论)。当然还有不稳定的基于 Oracle 成本的优化器计划 :(
  • 我还没有查看生成的 SQL。我还有一个关于交易编号和日期组合的唯一索引
  • 尝试删除TransactionNumbers.Contains(...) 条件,看看会发生什么。它应该处理更多的记录,但如果它运行得更快,那么问题就出在这种类型的过滤器上,这种过滤器在 EF(和一般情况下)中被认为是无效的——实际上应该是 SQL IN (...)
  • 如果我按照您的说法删除它,它会快得多,但根据您看到的要求计算不正确
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-08
相关资源
最近更新 更多