【问题标题】:Refactor GroupBy to avoid slowing down operation on big dataset重构 GroupBy 以避免在大数据集上减慢操作
【发布时间】:2015-06-14 17:04:54
【问题描述】:

我有一个大集合,我需要根据两个属性获取最新项目。 第一步是根据 date 属性对列表进行排序。这一切都很好而且很快。

然后我按两个属性对新列表进行分组,并从每个属性中取出第一项。

var one = Fisks.Where(s=>s.Havn.Id == 1).OrderByDescending(s=>s.Date);
var two = one.GroupBy(s=>new {s.Arter.Name, s.Sort});
var three = two.Select(s=>s.FirstOrDefault());

这可行,但是在大型集合上使用它时确实很慢。如何避免使用 groupBy 但仍然得到相同的结果?

谢谢!

【问题讨论】:

  • 尝试为数据库中的表添加索引以提高此类操作的性能。
  • 我认为您将不可接受的性能归因于查询的随机元素(GroupBy)。使用 Profiler 和 Showplan XML 事件捕获执行计划。
  • 我假设你明白这些的执行被推迟了?你确定是GroupBy 这么慢吗?
  • @usr 我已经尝试测试我的方式到它的速度很慢,而且我记得我对 SQL 查询的有限使用,GroupBy 真的很慢。但是我在哪里可以捕捉到它?一些链接?
  • google.com/… GroupBy 本身并不慢。也许您应该让自己更熟悉一般的查询调优。

标签: c# performance linq


【解决方案1】:

仅将 LINQ 用于第一步,然后在循环中执行第一个步骤,可以让您更好地控制流程并完全避免分组:

var query = Fisks
    .Where(f => f.Havn.Id == 1)
    .OrderByDescending(f => f.Date)
    .ThenBy(f => f.Arter.Name)
    .ThenBy(f => f.Sort);
var list = new List<Fisk>();
foreach (Fisk fisk in query) {
    if (list.Count == 0) {
        list.Add(fisk);
    } else {
        Fisk last = list[list.Count - 1];
        if (fisk.Sort != last.Sort || fisk.Arter.Name != last.Arter.Name) {
            list.Add(fisk);
        }
    }
}

【讨论】:

    【解决方案2】:

    一般来说,我建议不要在做一些可能会破坏该顺序的事情之前进行排序(例如 GroupBy 可以在 SQL 中执行,由 LINQ2SQL 生成)。也尝试只订购你将要使用的东西。如果您仅限制选择所需的字段/属性,则可以提高查询性能。你可以摆弄这个sample 并改用你的 real 后端:

    var Fisks=new[]{
        new {Havn=new{Id=1},Date=DateTime.MinValue,Arter=new{Name="A"},Sort=1,Title="A1"},
        new {Havn=new{Id=1},Date=DateTime.MinValue.AddDays(1),Arter=new{Name="A"},Sort=1,Title="A2"},
        new {Havn=new{Id=1},Date=DateTime.MinValue,Arter=new{Name="B"},Sort=1,Title="B1",},
        new {Havn=new{Id=1},Date=DateTime.MinValue.AddDays(2),Arter=new{Name="B"},Sort=1,Title="B2",},
        new {Havn=new{Id=1},Date=DateTime.MinValue.AddDays(2),Arter=new{Name="B"},Sort=1,Title="B3",},
    };
    var stopwatch=Stopwatch.StartNew();
    var one = Fisks.Where(s=>s.Havn.Id == 1).OrderByDescending(s=>s.Date);
    var two = one.GroupBy(s=>new {s.Arter.Name, s.Sort});
    var three = two.Select(s=>s.FirstOrDefault());
    var answer=three.ToArray();
    stopwatch.Stop();
    stopwatch.ElapsedTicks.Dump("elapsed Ticks");
    answer.Dump();
    
    stopwatch.Restart();
    answer=Fisks
    .Where(f=>f.Havn.Id.Equals(1))
    .GroupBy(s=>new {s.Arter.Name, s.Sort},(k,g)=>new{
        s=g.OrderByDescending(s=>s.Date).First()//TOP 1 -> quite fast
    })
    .Select(g=>g.s)
    .OrderByDescending(s=>s.Date) // only fully order results
    .ToArray();
    stopwatch.Stop();
    stopwatch.ElapsedTicks.Dump("elapsed Ticks");
    answer.Dump();
    

    如果您使用任何 SQL Server,您应该在 LINQPad 中检查生成的 SQL。您不想以n+1 Query 结尾。在Havn.IdFisks.Date 上建立索引也可能会有所帮助。

    【讨论】:

    • 有没有一种很酷的方法可以在 linqpad 中检查查询的运行时间?不是时间,而是大O。
    • 使用你提供的最后一个查询运行的时间比我的慢五秒,所以它似乎没有什么区别:)
    • 您真的应该检查生成的 SQL 并调整查询。您还可以复制 SQL 并使用 SSMS 查询分析器。
    • 你可以从生成的 SQL 中估计出大 O - 如果你得到一些基本查询和多个通过 id 访问的查询,你就会陷入 n+1 陷阱。
    猜你喜欢
    • 2018-01-07
    • 2023-02-07
    • 1970-01-01
    • 1970-01-01
    • 2017-06-17
    • 2018-06-04
    • 2020-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多