【问题标题】:Table Join performance issue with Entity Framework实体框架的表连接性能问题
【发布时间】:2014-05-11 16:38:27
【问题描述】:

加入两个表会导致选择时间从 330 秒增加到 40 秒。将要连接的表只包含一个 ID 和文本。没想到加入两个表的时候select时间会增加8倍。我的 JOIN 有什么问题还是这是正常的 SQL Server 行为?

主表填满了 3500 万条记录,以查看在达到 10 GB 的 SQL Server Express 限制时它的执行情况。在字段 LogTimeStamp 和字段 LogType 上创建了一个附加索引。

连接表的内容:

var queryList = messages
    .Join(types,
    type => type.LogType,
    typeText => typeText.LogType, (msg, msgType) => new
    {
        msg.LogID,
        msg.LogTimeStamp,
        msg.LogUser,
        msg.LogType,
        msgType.LogTypeName,
        msg.LogMessage
    })
    .Where(t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp) >= fromDate)
    .Where(t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp) <= toDate)
    .Where(t => t.LogType != 4)
    .OrderBy(m => m.LogID)
    .ToList();

使用生成的 SQL

SELECT 
  1 AS [C1], 
  [Extent1].[LogID] AS [LogID], 
  [Extent1].[LogTimeStamp] AS [LogTimeStamp], 
  [Extent1].[LogUser] AS [LogUser], 
  [Extent1].[LogType] AS [LogType], 
  [Extent2].[LogTypeName] AS [LogTypeName], 
  [Extent1].[LogMessage] AS [LogMessage]
  FROM  [dbo].[AuditTrailMessages] AS [Extent1]
  INNER JOIN [dbo].[AuditTrailLogTypes] AS [Extent2] ON [Extent1].[LogType] = [Extent2].[LogType]
WHERE ((convert (datetime2, convert(varchar(255), [Extent1].[LogTimeStamp], 102) ,  102)) >= @p__linq__0) 
  AND ((convert (datetime2, convert(varchar(255), [Extent1].[LogTimeStamp], 102) ,  102)) <= @p__linq__1) 
  AND ( NOT ((4 =  CAST( [Extent1].[LogType] AS int)) AND ( CAST( [Extent1].[LogType] AS int) IS NOT NULL)))  

相比

var queryList = messages
    .Where(t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp) >= fromDate)
    .Where(t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp) <= toDate)
    .Where(t => t.LogType != 4)
    .OrderBy(m => m.LogID)
    .ToList();

生成的 SQL

SELECT 
  [Extent1].[LogID] AS [LogID], 
  [Extent1].[LogTimeStamp] AS [LogTimeStamp], 
  [Extent1].[LogUser] AS [LogUser], 
  [Extent1].[LogMessage] AS [LogMessage], 
  [Extent1].[LogType] AS [LogType]
  FROM [dbo].[AuditTrailMessages] AS [Extent1]
  WHERE ((convert (datetime2, convert(varchar(255), [Extent1].[LogTimeStamp], 102) ,  102)) >= @p__linq__0) 
  AND ((convert (datetime2, convert(varchar(255), [Extent1].[LogTimeStamp], 102) ,  102)) <= @p__linq__1) 
  AND ( NOT ((4 =  CAST( [Extent1].[LogType] AS int)) AND ( CAST( [Extent1].[LogType] AS int) IS NOT NULL)))

【问题讨论】:

    标签: c# sql sql-server entity-framework join


    【解决方案1】:

    你看到所有这些了吗:

    ((convert (datetime2, convert(varchar(255), [Extent1].[LogTimeStamp], 102)

    他们很糟糕。特别糟糕。他们基本上说“不要使用索引,进行全表扫描”。

    他们跑到你做:

    t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp) >= fromDate

    这不好。它不是必需的。日期中的任何时间戳都大于或等于每个定义的日期并小于下一个日期。

    所以:

    t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp) >= fromDate

    变成

    t => t.LogTimeStamp >= fromDate

    t => System.Data.Entity.DbFunctions.TruncateTime(t.LogTimeStamp)

    变成

    t => t.LogTimeStamp

    .Where(t => t.LogType != 4)

    看起来像类型不匹配 - 让我猜猜,它不是数据库中的 int。然后使用 Equals 方法。这是 EF 中的一个已知错误。但这应该没关系 - 此时您应该减少到相当多的条目,您的问题可能是日期时间比较的超级低效代码。

    切勿在比较的字段侧执行函数。绝不。它们会杀死任何索引的使用(除非有一个恰好具有此功能的索引)。始终重写查询以使所有函数都在常量端。

    不是 EF 问题 - 一般的 SQL 初学者错误。

    【讨论】:

    • 解决了这个问题。 .AddDays() 不可能在 LINQ 内完成。但这不是问题,我只是将 toDate = toDate.AddDays(1);在查询之前。 LogType 转换是因为查询的本地临时更改,因为使用使用的局部变量,在这里发布它看起来真的很难看。即使有这个变量,即使有表连接,select 语句现在也能在 0.5 秒内完成!
    • @MarkusEgle 那是因为基本上日期上的连接会取出大多数行,所以最后的行会被强制执行,这并不重要。 AddDAYs 的好消息——我也这样做(预先计算)。请记住这一点:永远不要在字段方面进行计算(作为硬规则 - 有例外,但很少),因为命中会杀死任何索引使用。 ORM 与否 ;)
    • 场边是什么意思?
    猜你喜欢
    • 1970-01-01
    • 2011-11-16
    • 2016-03-09
    • 2014-03-08
    • 2011-12-14
    相关资源
    最近更新 更多