【问题标题】:How best to optimise this small bit of c# Linq code如何最好地优化这一小段 c# Linq 代码
【发布时间】:2012-02-06 12:36:00
【问题描述】:

如果您查看我们的街机主页:

http://www.scirra.com/arcade

右上角有一个框,显示最后玩此游戏的人。在我使用的分析器中,它显示这需要 900 毫秒才能运行,这大约是总页面加载时间的 80%。

查询比较简单:

// Recent players
using (MainContext db = new MainContext())
{
    var q = (from c in db.tblArcadeGamePlays
                join a in db.tblProfiles on c.UserID equals a.UserID
                where c.UserID != 0
                select new
                {
                    c.UserID,
                    c.tblForumAuthor.Username,
                    a.EmailAddress,
                    Date = (from d in db.tblArcadeGamePlays where d.UserID == c.UserID orderby d.Date descending select new { d.Date }).Take(1).Single().Date
                })
    .Distinct()
    .OrderByDescending(c => c.Date)
    .Take(16);

但它对我的需求来说太慢了。

在此设置输出缓存不合适,因为此框最好是实时的。此外,在正常页面加载之上的 900 毫秒对于一个用户来说也太慢了,因此希望尽可能避免这种情况。

有人对我如何加快速度有任何想法吗?我目前的两个想法是:

  • 一个新的数据库表,其中包含最后的玩家,无需加入
  • 存储在某处的字段保存该框的 HTML,每次发生的新游戏都会重建该字段
  • 两者的结合

两者都很丑!任何帮助表示赞赏。

根据要求,linqpad 结果

Lambda

TblArcadeGamePlays
   .Join (
      TblProfiles, 
      c => c.UserID, 
      a => a.UserID, 
      (c, a) => 
         new  
         {
            c = c, 
            a = a
         }
   )
   .Where (temp0 => (temp0.c.UserID != 0))
   .Select (
      temp0 => 
         new  
         {
            UserID = temp0.c.UserID, 
            Username = temp0.c.User.Username, 
            EmailAddress = temp0.a.EmailAddress, 
            Date = TblArcadeGamePlays
               .Where (d => (d.UserID == temp0.c.UserID))
               .OrderByDescending (d => d.Date)
               .Select (
                  d => 
                     new  
                     {
                        Date = d.Date
                     }
               )
               .Take (1)
               .Single ().Date
         }
   )
   .Distinct ()
   .OrderByDescending (c => c.Date)
   .Take (16)

SQL

-- Region Parameters
DECLARE @p0 Int = 0
-- EndRegion
SELECT TOP (16) [t6].[UserID], [t6].[Username], [t6].[EmailAddress], [t6].[value] AS [Date2]
FROM (
    SELECT DISTINCT [t5].[UserID], [t5].[Username], [t5].[EmailAddress], [t5].[value]
    FROM (
        SELECT [t0].[UserID], [t2].[Username], [t1].[EmailAddress], (
            SELECT [t4].[Date]
            FROM (
                SELECT TOP (1) [t3].[Date]
                FROM [tblArcadeGamePlays] AS [t3]
                WHERE [t3].[UserID] = [t0].[UserID]
                ORDER BY [t3].[Date] DESC
                ) AS [t4]
            ) AS [value]
        FROM [tblArcadeGamePlays] AS [t0]
        INNER JOIN [tblProfile] AS [t1] ON [t0].[UserID] = [t1].[UserID]
        INNER JOIN [tblForumAuthor] AS [t2] ON [t2].[Author_ID] = [t0].[UserID]
        ) AS [t5]
    WHERE [t5].[UserID] <> @p0
    ) AS [t6]
ORDER BY [t6].[value] DESC

查询计划

【问题讨论】:

  • 尝试使用 group by 而不是 distinct。
  • 我假设这是 LINQ to SQL 或实体框架?
  • 您还可以使用 SQL Profiler 来准确找出正在生成的查询以及需要多长时间。如果您从未使用过 SQL Profiler,这里是一个非常好的tutorial by Brent Ozar
  • @Carlos 谢谢,虽然我没有这个版本的分析器,但我可能会购买开发版进行开发,因为我认为它包含它并且现在可能对我有用
  • 对于您试图从数据库中获取的信息,这似乎是一个非常复杂的查询。可能值得尝试用 SQL 编写它并从它向后工作到 LINQ?

标签: c# asp.net performance linq optimization


【解决方案1】:

我敢打赌,您看到的几乎所有延迟都来自数据库本身,而不是 LINQ(这是一个数据库优化问题,而不是 LINQ 优化问题)。

我会使用 linqpad 查看正在生成的查询(请参阅:http://www.thereforesystems.com/view-t-sql-query-generated-by-linq-to-sql-using-linqpad/),并将其发布在此处。在 SQL Management Studio 中运行该查询的查询计划(假设您使用的是 SQL Server)也会有所帮助。

好的,鉴于编辑,尝试这样的事情。它应该大大简化查询:

using (MainContext db = new MainContext())
{
    var latestIds = db.tblArcadeGamePlays.OrderByDescending(c => c.Date).Select(c => c.UserID).Distinct().Take(16); // These are the 16 most recent player Ids.
    // join them here to the rest of those player's data
    var playerData = ... // you'll need to fill in here some by filtering the other data you want using latestIds.Contains
}

【讨论】:

  • 嗨,克里斯,很棒的工具,感谢您的链接!我已经在问题中发布了 linqpad 结果。我现在去查查询计划
  • 哇,我的眼睛在燃烧。我几乎希望我没有问!
  • 刚刚添加了查询计划,我是这些功能的新手,所以我希望我发布了正确的内容。其中 30% 似乎是 tblForumAuthor 上的索引搜索,另外 25% 用于排序,不确定到底是什么
  • 大声笑,如果筛选太多,请不要担心,但非常感谢您提供的任何帮助!
  • @TomGullen 你试过我的简化日期查询,看看是否有什么不同吗?
【解决方案2】:

这可能不是问题,但您的日期查询可以简化为:

Date = (from d in db.tblArcadeGamePlays 
        where d.UserID == c.UserID 
        orderby d.Date descending 
        select d.Date).First()

也许迂回查询使优化器感到困惑。否则我同意其他答案,请检查生成的 SQL 查询并检查您的索引。

另外,您确定Distinct 是必要的并且它会按照您的想法进行吗?它只会过滤掉每个字段/列具有相同值的重复项。

【讨论】:

  • 谢谢,这节省了大约 15% 的总执行时间!
  • @TomGullen 很酷。尝试删除Distinct 和a)查看结果是否不同(最重要)和b)查看它是否更快。我认为它会更快,我很确定结果会相同,但不是 100% 确定
  • 无法删除不同的,否则我们会得到相同的播放器。例如,如果我连续玩 3 场游戏,如果我只希望出现一次,我的名字会出现 3 次。
  • @TomGullen 嗯,所以我猜 tblProfilestblArcadeGamePlays 是一对多的,但这似乎也是您需要加入 tblArcadeGamePlays 的唯一原因到达tblForumAuthortblForumAuthor 是否有指向 tblProfiles 的外键,它们是一对一的吗?如果是这样,您可以跳过连接中的tblArcadeGamePlays,这样您就不需要Distinct。我认为Distinct 调用是造成缓慢的部分原因,所以如果你能避免它可能会有所帮助。
  • 是的,你是对的,tblProfilestblAuthor 是一对一的关系,这是我早期做出的一个设计选择(不好的选择),我很早就将论坛软件分开了到我的网站。 tblArcadeGamePlays 中的 UserID 可以引用 tblProfile.UserIDtblForumAuthor.Author_ID 但尚未与 tblForumAuthor 建立关系
【解决方案3】:

确保连接的两边都有索引。

【讨论】:

    【解决方案4】:

    使用这些查询针对您的数据库运行 SQL Server 数据库调整向导(在 SQL Management Studio/工具中),并让它创建统计信息和索引来调整您的数据库的性能。 有多少人会推荐这个?它有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 2016-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-15
      • 1970-01-01
      相关资源
      最近更新 更多