【问题标题】:EF6 SQL generation for <where nullable columns equals><where nullable columns equals> 的 EF6 SQL 生成
【发布时间】:2013-11-09 11:14:43
【问题描述】:

尝试从 EF5 升级到 EF6,我遇到了可空列搜索表的显着性能差距。这是一个示例:

public class Customer
{
    public int Id { get; set; }
    public int? ManagerId { get; set; }
    //public virtual Manager Manager { get; set; }
}

public class MyContext : DbContext
{
    public MyContext(string connstring): base(connstring){}
    public DbSet<Customer> Customers { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var db = new MyContext("CONNSTRING");
        var managerId = 1234;
        var q = from b in db.Customers
                where b.ManagerId == managerId
                select b.Id;
        var s = q.ToString();
    }
}

EF6在生成SQL的时候,会增加一些空值处理的逻辑:

SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE (([Extent1].[ManagerId] = @p__linq__0) 
AND ( NOT ([Extent1].[ManagerId] IS NULL OR @p__linq__0 IS NULL))) 
OR (([Extent1].[ManagerId] IS NULL) AND (@p__linq__0 IS NULL))

注意相同的 linq 在 EF5 下生成更简单的 SQL:

SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Customers] AS [Extent1]
WHERE [Extent1].[ManagerId] = @p__linq__0

我可以理解开发人员试图实现的观点:如果您提供 null 作为参数,则 where managerId = null 的查询将不会选择任何行。感谢您的关心,但 99.9% 的搜索逻辑是分开的:一个用例查找 where ManagerId == null,另一个用例搜索特定 id where ManagerId == managerId

问题是对性能的影响很大:MS SQL 没有在 ManagerId 上使用索引,并且发生了表扫描。我的项目有数百个类似的搜索,数据库大小约为 100GB,升级到 EF6 后整体性能减少了大约 10。

问题是有人知道在 EF6 中禁用此障碍并生成简单 sql 的某种配置或约定吗?

编辑:

我在我的项目中检查了十几个类似的选择,发现:

  • 在某些情况下,SQL SERVER 确实使用为字段 I 指定的索引 搜索。即使在这种情况下,也会有轻微的性能损失:它 使用索引两次:第一次寻找我在 参数,第二次找null
  • EF6 甚至在常量被准确指定为非空时检查空,例如:

                from p in db.PtnActivations
            where p.Carrier != "ALLTEL"
            where p.Carrier != "ATT"
            where p.Carrier != "VERIZON"
    

生成 SQL

    WHERE ( NOT (('ALLTEL' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL))) AND ( NOT (('ATT' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL))) AND ( NOT (('VERIZON' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL)))

没有使用我在运营商上的索引。 EF5版本有

( NOT (('ALLTEL' = [Extent1].[Carrier]))) AND ( NOT (('ATT' = [Extent1].[Carrier]))) AND ( NOT (('VERIZON' = [Extent1].[Carrier]) ))

使用它。

注意条件('ALLTEL' = [Extent1].[Carrier]) AND ([Extent1].[Carrier] IS NOT NULL)。第二部分总是 false,但添加这部分会放弃索引。

我对大约 170 万条记录的例行导入(通常需要大约 30 分钟)已经持续了 3 个小时,进度约为 30%。

【问题讨论】:

  • 我看不出两个 SQL 选择有什么不同。他们应该选择相同的行集。如果ManagerId 上没有索引,我看不出MySQL 在这两种情况下如何使用与表扫描不同的策略。您确定性能下降是由此造成的,而不是其他原因吗?
  • wilx,上面的数据库模式是人为的,但模式很常见:一个表在另一个表上有可为空的外键(当然还有索引)。当 select 很简单时,MS SQL 确实使用索引,例如 where managerid = parameter 并且不要在 EF6 情况下使用它。我有每个 db 操作的性能时间的 sql 日志(使用 EF5 中的 EFTracingProvider 和 EF6 中的 DBCommandInterceptor 实现),我看到了确切的瓶颈。为了验证,我在 MSQL SMS 中运行了一个 sql 并检查了两种情况下的计划

标签: entity-framework entity-framework-6


【解决方案1】:

设置

db.Configuration.UseDatabaseNullSemantics = true;

获取您在 EF5 中的行为。这个workitem 描述了truefalse 之间的区别,应该可以帮助您确定您是否接受旧的行为。

【讨论】:

  • 太棒了!它可以按需要工作。我认为微软应该在 EF6 中发布一个大警告,例如“如果您打算将 EF 用于 1gb+ 数据库,请考虑特定的性能配置”
【解决方案2】:

答案截然不同

如果您使用的是 varchar(xxx),则 LNQ 到 SQL 会输出 nvarchar(4000),这会破坏索引和转换,从而大大破坏您的 sql 计划。就我而言,由于奇怪的 null 行为,我发现了这个问题,但这不是问题。下面的答案解决了 null 和 nvarchar 问题。 SQL 计划从 ~11 变为 0.006。

public class InterestingRow
{
    [Key]
    public int interesting_row_id { get; set; }

    [StringLength(255), Required, Column(TypeName = "varchar")]
    public string public_guid { get; set; }
}

(是的,使用 varchar 的原因有很多,比如您要存储公开的 guid)

【讨论】:

  • 在现有数据库中首先使用 EF6 代码。我必须设置“.HasColumnType(columnTypeString)”,我猜这正是原因。感谢您的替代答案!
猜你喜欢
  • 2020-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多