【问题标题】:EF 4.1: Why does turning a constant into a variable result in extra sub query?EF 4.1:为什么将常量转换为变量会导致额外的子查询?
【发布时间】:2011-05-08 02:31:58
【问题描述】:

今天我发现 Entity Framework 在它生成的 SQL 中添加了一个不必要的子查询。我开始挖掘我的代码,试图缩小它可能来自哪里。 A(很长)稍后我指出是什么原因造成的。但是现在我比开始时更困惑,因为我不知道为什么会导致它。

基本上我发现,在某些情况下,只需将常量转换为变量就可以改变 Entity Framework 生成的 SQL。我已将所有内容压缩到最低限度,并将其打包到一个小控制台应用程序中:

using System;
using System.Data.Entity;
using System.Linq;

class Program
{
    private static readonly BlogContext _db = new BlogContext();

    static void Main(string[] args)
    {
        const string email = "foo@bar.com";

        var comments = from c in _db.Comments
                       where c.Email == email
                       select c;

        var result = (from p in _db.Posts
                      join c in comments on p.PostId equals c.PostId
                      orderby p.Title
                      select new { p.Title, c.Content });

        Console.WriteLine(result);
    }
}

public class BlogContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
}

public class Comment
{
    public int CommentId { get; set; }
    public int PostId { get; set; }
    public string Email { get; set; }
    public string Content { get; set; }
}

这显示了以下输出,非常完美:

SELECT
[Extent1].[PostId] AS [PostId],
[Extent1].[Title] AS [Title],
[Extent2].[Content] AS [Content]
FROM  [dbo].[Posts] AS [Extent1]
INNER JOIN [dbo].[Comments] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[PostId]
WHERE N'foo@bar.com' = [Extent2].[Email]
ORDER BY [Extent1].[Title] ASC

现在,如果我将 email 设为变量:

/*const*/ string email = "foo@bar.com";

输出发生了根本性的变化:

SELECT
[Project1].[PostId] AS [PostId],
[Project1].[Title] AS [Title],
[Project1].[Content] AS [Content]
FROM ( SELECT
        [Extent1].[PostId] AS [PostId],
        [Extent1].[Title] AS [Title],
        [Extent2].[Content] AS [Content]
        FROM  [dbo].[Posts] AS [Extent1]
        INNER JOIN [dbo].[Comments] AS [Extent2] ON [Extent1].[PostId] = [Extent2].[PostId]
        WHERE [Extent2].[Email] = @p__linq__0
)  AS [Project1]
ORDER BY [Project1].[Title] ASC

附带说明,LINQ to SQL 似乎没有这样做。我知道忽略这一点可能没问题,因为两个命令都返回相同的数据。但我非常好奇为什么会发生这种情况。直到今天,我一直有一个(也许是假的?)印象,将常量转换为变量总是安全的,只要值保持不变(在这种情况下确实如此)。所以我不得不问...

为什么一个看似微不足道的变化会导致生成的 SQL 产生如此大的差异?

更新:

为了清楚起见,我的问题不在于 email 的值是第一个查询中的硬编码值和第二个查询中的变量(这在世界上很有意义)。我的问题是为什么变量版本会导致额外的子查询。

谢谢!

【问题讨论】:

  • 我不会担心查询的 form - SQL 旨在表达您想要的结果,而不是实现它们的过程 - 我如果查询计划有很大或任何差异,我会感到惊讶。优化器应该产生大致相同的查询计划。

标签: c# entity-framework entity-framework-4 linq-to-entities code-first


【解决方案1】:

答案相当简单。您的 LINQ 查询用表达式树表示。 const 变量与非 const 变量的区别在于ConstantExpressionParameterExpression.

当您使用 const 时,您的 LINQ 查询对此变量使用 ConstExpression,当您使用非 const 时,它使用 ParameterExpression,EF 运行时对它们的解释不同。

常量实际上意味着该值永远不会改变,并且该值可以内联到查询中。

【讨论】:

  • 这提醒我,我还有很多关于 LINQ 的知识,特别是表达式树。非常感谢这些链接,伊万。
  • 这可能真的很重要。如果你说它是一个常量,而它不是,那么它每次都会在 SQL Server 中生成并缓存一个全新的查询。另一方面,如果它是一个变量,查询本身可能是相同的,尽管变量的值不同,所以它只会被生成和缓存一次。所以......如果随着时间的推移,查询将以不同的值运行,那么将其保留为变量。如果它在您的应用程序中确实是常量,则使用常量。很简单。
【解决方案2】:

不是问题的答案 - 只是使用参数的上下文。

这与创建一个查询有关,以便它可以重用现有的查询计划。

如果您将变量(而不是对参数的引用)注入到生成的 SQL 中,那么当变量更改时,SQL Server(可能还有其他数据库引擎)将无法重用相同的计划。

对于常量这不是问题,因为您知道值总是相同的,但对于变量,每次执行查询时,SQL 和查询计划都会略有不同。

这听起来可能不多,但 SQL 只为查询计划分配了一定数量的空间,因此缓存中有成百上千的细微变化可以说是真正的“空间浪费”!

【讨论】:

  • +1 我认为这就是问题的答案。此外,它看起来像是对最大的 EF 谜团之一的非常清楚的解释。
【解决方案3】:

这实际上在 SQL 中有很大的不同吗?内部查询与原始查询相同,外部查询只是内部查询的一个包装器,不会更改结果集。

除非这会引起问题,否则我个人不会担心。两种查询方式的查询计划是否不同?我的猜测是它们是相同的。

【讨论】:

  • 谢谢威尔!好吧,在这种情况下,差异可能可以忽略不计。但我正在处理的实际代码是一个 更大的应用程序的数据访问层,它使 IQueryables 无处不在。我只是担心,如果像这样的小改动会导致生成额外的查询,那么可能更大的改动最终会在未来导致一些令人讨厌的 T-SQL 混乱。我关心这个的主要原因是因为我喜欢使用 LINQ 并计划继续使用它(当事情变得混乱时不必求助于存储过程。)
  • @Daniel - 你实际上并没有在这里获得额外的查询 - 你仍然只有 一个 查询 - 尽管看起来稍微复杂一点的查询。 SQL Server 优化查询的外部部分没有问题 - 老实说!
  • 是的,我知道数据库只被命中一次,是的,我真的很想相信 SQL Server 足够聪明,可以确定外部查询是多余的。我就是忍不住,需要询问并确保:)
  • 可能会对性能产生影响。我刚刚在相当长的查询中遇到了速度问题。将 string 更改为 const string 实际上会使查询速度提高 10 倍。在任一实例中都只生成了一个查询。
【解决方案4】:

就像人们说的那样。两个查询之间的差异很小。

原因是在创建 LINQ 时创建的表达式在使用变量和常量时是不同的。 EF 会捕捉到这一点,并相应地生成您的 SQL。它知道它永远不会改变,因此可以将其硬编码到查询中以获得(可能的)性能提升。

编辑: 我认为除了“EF 就是这样”之外,这个问题没有答案。但众所周知,EF 喜欢创建许多子选择。对于更复杂的查询,它可能会导致许多子选择。有些人甚至对这个事实不屑一顾,甚至使用 EF。但这只是使用像 EF 这样的工具的代价。你失去了对某些东西的细粒度控制,这可能会带来很大的性能提升。当您可以使用 C 并获得更多性能时,为什么还要使用 .NET?既然可以使用汇编,为什么还要使用 C 来获得更多的性能提升?

确保安全并仍然能够使用高抽象层 EF 的唯一方法是经常使用 SQL 分析器并检查是否存在对真实数据花费太长时间的查询。如果你找到了一些,那么要么将它们转换为直接 SQL 或存储过程。

【讨论】:

  • 谢谢欣快。是的,我注意到在使用常量时该值是硬编码的。我理解这一点,这很有意义。我的问题是关于子查询的。
猜你喜欢
  • 2021-11-07
  • 2021-08-26
  • 2014-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多