【问题标题】:What can I do to improve the speed of this query?我能做些什么来提高这个查询的速度?
【发布时间】:2015-03-13 12:41:07
【问题描述】:

我有一个 linq 查询,它根据页面点击量表返回用户查看的最后一页。这些字段只是从用户活动记录的TimeStampUserIDURL。查询如下所示:

public static IQueryable GetUserStatus()
{
    var ctx = new AppEntities();
    var currentPageHits = ctx.Pagehits
        .GroupBy(x => x.UserID)
        .Select(x => x.Where(y => y.TimeStamp == x.Max(z => z.TimeStamp)))
        .SelectMany(x => x);

    return currentPageHits.OrderByDescending(o => o.TimeStamp);
}

查询运行良好,但运行缓慢。我们的 DBA 向我们保证,该表在所有正确的位置都有索引,问题一定出在查询上。

这有什么本质上的错误或坏处,还是有更有效的方法来获得相同的结果?

【问题讨论】:

  • 这似乎是一个非常简单的查询...我认为它不能优化很多。
  • 如果我错了,请纠正我,但 PLINQ(并行 LINQ)在这里不是一个可行的选择吗? msdn.microsoft.com/en-us/library/dd460688%28v=vs.110%29.aspx
  • 您应该记录查询生成的 SQL 并对其进行分析以确保其有效。
  • @GrawCube 他使用的是Entity Framework或LINQ to SQL,所以分组实际上是在DB中完成的。当您有多个可以并行化的工作单元时,PLINQ 会有所帮助。
  • 如 w0lf 所说。最终的 OrderByDescending 最多可以在 C# 端完成,但 PLINQ 在这里无济于事。

标签: c# linq-to-sql


【解决方案1】:

你可以试试:

var currentPageHits2 = ctx.Pagehits
    .GroupBy(x => x.UserID)
    .Select(x => x.OrderByDescending(y => y.TimeStamp).First())
    .OrderByDescending(x => x.TimeStamp);

但是速度应该是一样的。

请注意,此查询与您的查询之间存在细微差别...对于您的查询,如果 UserId 有两个“最大 TimeStampPageHits em> 具有相同的TimeStamp,会返回两行,只有这一行。

【讨论】:

  • 我不知道在这种情况下是否保持顺序,但在 Linq-To-Objects 中这会更好。 ctx.Pagehits.OrderByDescending(x=> x.TimeStamp).GroupBy(x=> x.UserID).Select(g=> g.First())
  • @TimSchmelter 是的,因为在 LINQ-to-objects 中 GroupBy 保证保持顺序。但我认为它在“翻译”中迷失了
  • @TimSchmelter 来自 MSDN:The query behavior that occurs as a result of executing an expression tree that represents calling GroupBy<TSource, TKey>(IQueryable<TSource>, Expression<Func<TSource, TKey>>) **depends on the implementation of the type of the source parameter**. The expected behavior is that it groups the elements of source by a key value that is obtained by invoking keySelector on each element.*
  • 累积。 “细微差别”,基本上是DENSE_RANK OVER (PARTITION BY UserID ORDER BY TimeStamp DESC) OP 试图用 LINQ 做什么。然后他想只取那些返回 1 的,所以每个用户的最新时间戳。要获得相同的行为,您可以将 Select 替换为 .SelectMany(x => x.GroupBy(y => y.TimeStamp).OrderByDescending(g=> g.Key).First())
  • @TimSchmelter 非常棒的查询 :-) 你应该回复
【解决方案2】:

所以你尝试用 LINQ 实现DENSE_RANK() OVER (PARTITION BY UserID ORDER BY TimeStamp DESC)?所以每个用户组的所有最新记录都根据Timestamp。你可以试试:

public static IQueryable GetUserStatus()
{
    var ctx = new AppEntities();
    var currentPageHits = ctx.Pagehits
        .GroupBy(x => x.UserID)
        .SelectMany(x => x.GroupBy(y => y.TimeStamp).OrderByDescending(g=> g.Key).FirstOrDefault())
        .OrderByDescending(x => x.TimeStamp);

    return currentPageHits;
}

所以它按TimeStamp 对用户组进行分组,然后它采用最新的组(一个或多个记录,以防平局)。 SelectMany 将组展平为记录。我认为这比您的查询更有效。

【讨论】:

  • 蒂姆,那肯定更快,至少 2 秒缩短了 10 秒的查询,谢谢。不得不将 .First() 更改为 .FirstOrDefault()
  • @GordonCopestake:你为什么需要FirstOrdefault?由于您在user-group 中,因此必须至少有一条记录。如果我将 TimeStamp 分组到一个子组中,我至少会得到一个记录。
  • First() 给出一个 YSOD 和 The method 'First' can only be used as a final query operation. Consider using the method 'FirstOrDefault' in this instance instead.
  • @GordonCopestake:啊,好的。这是一个问题。本主题:stackoverflow.com/questions/18232055/…
  • 谢谢蒂姆,我会将您的答案标记为正确,因为我猜此类问题没有明确正确的答案。至少没有人说过“天哪,这是一个糟糕的查询,你做错了!”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-04-19
  • 1970-01-01
  • 2013-01-18
  • 2011-02-12
  • 1970-01-01
  • 2010-09-14
  • 1970-01-01
相关资源
最近更新 更多