与非常相似的查询的EF性能差异很大

时间:2011-03-05 17:02:34

标签: .net entity-framework

我们有两个实体框架查询,一个Include一个查询独立查询。他们在这里

        ConfigModelContainer model = new ConfigModelContainer();
        var scope = model.Scopes.Include("Settings")
            .Where(s => (s.Level == intLevel && s.Name == name))
            .First();

        ConfigModelContainer model = new ConfigModelContainer();
        var scope = model.Scopes
            .Where(s => (s.Level == intLevel && s.Name == name))
            .First();
        var settings = model.Settings.Where(s => s.Scope.Id == scope.Id).ToList();

另一个与第一个具有相同性能的案例(Query2)

        var scope1 = model.Scopes
            .Where(s => (s.Level == intLevel && s.Name == name))
            .First();
        scope1.Settings.Load();

第一个运行30秒,第二个运行亚秒。这太奇怪了,我没有想法。

有谁知道为什么会这样?

编辑:实际的TSQL查询运行速度非常快(亚秒级)

编辑2 :以下是查询:

首先:

SELECT 
[Project2].[Level] AS [Level], 
[Project2].[Id] AS [Id], 
[Project2].[Name] AS [Name], 
[Project2].[ParentScope_Id] AS [ParentScope_Id], 
[Project2].[C1] AS [C1], 
[Project2].[Id1] AS [Id1], 
[Project2].[Type] AS [Type], 
[Project2].[Value] AS [Value], 
[Project2].[Scope_Id] AS [Scope_Id]
FROM ( SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[Name] AS [Name], 
    [Limit1].[Level] AS [Level], 
    [Limit1].[ParentScope_Id] AS [ParentScope_Id], 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[Type] AS [Type], 
    [Extent2].[Value] AS [Value], 
    [Extent2].[Scope_Id] AS [Scope_Id], 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM   (SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Name] AS [Name], 
        [Extent1].[Level] AS [Level], 
        [Extent1].[ParentScope_Id] AS [ParentScope_Id]
        FROM [dbo].[Scopes] AS [Extent1]
        WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1) ) AS [Limit1]
    LEFT OUTER JOIN [dbo].[Settings] AS [Extent2] ON [Limit1].[Id] = [Extent2].[Scope_Id]
)  AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC

第二

SELECT 
[Limit1].[Level] AS [Level], 
[Limit1].[Id] AS [Id], 
[Limit1].[Name] AS [Name], 
[Limit1].[ParentScope_Id] AS [ParentScope_Id]
FROM ( SELECT TOP (1) 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Level] AS [Level], 
    [Extent1].[ParentScope_Id] AS [ParentScope_Id]
    FROM [dbo].[Scopes] AS [Extent1]
    WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1)
)  AS [Limit1]

SELECT 
1 AS [C1], 
[Extent1].[Id] AS [Id], 
[Extent1].[Type] AS [Type], 
[Extent1].[Value] AS [Value], 
[Extent1].[Scope_Id] AS [Scope_Id]
FROM [dbo].[Settings] AS [Extent1]
WHERE [Extent1].[Scope_Id] = @EntityKeyValue1

第三

SELECT 
[Limit1].[Level] AS [Level], 
[Limit1].[Id] AS [Id], 
[Limit1].[Name] AS [Name], 
[Limit1].[ParentScope_Id] AS [ParentScope_Id]
FROM ( SELECT TOP (1) 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Level] AS [Level], 
    [Extent1].[ParentScope_Id] AS [ParentScope_Id]
    FROM [dbo].[Scopes] AS [Extent1]
    WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1)
)  AS [Limit1]

SELECT 
1 AS [C1], 
[Extent1].[Id] AS [Id], 
[Extent1].[Type] AS [Type], 
[Extent1].[Value] AS [Value], 
[Extent1].[Scope_Id] AS [Scope_Id]
FROM [dbo].[Settings] AS [Extent1]
WHERE [Extent1].[Scope_Id] = @p__linq__0

编辑3

我无法在同一台机器上继续测试。这是更快的机器上的结果。这是代码和结果:

    static void Main(string[] args)
    {
        int intLevel = 2;
        string name = "fb226050-4f92-4fca-9442-f76565b33877";
        Stopwatch sw = new Stopwatch();
        using (CMEntities model = new CMEntities())
        {
            sw.Start();
            for (int i = 0; i < 5; i++)
            {

                var scope1 = model.Scopes.Include("Settings")
                   .Where(s => (s.Level == intLevel && s.Name == name))
                   .First();

                Console.WriteLine("Query:1, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
                sw.Reset();
                sw.Start();
            }
        }
        Console.WriteLine();
        using (CMEntities model = new CMEntities())
        {
            sw.Start();
            for (int i = 0; i < 5; i++)
            {

                var scope1 = model.Scopes
                   .Where(s => (s.Level == intLevel && s.Name == name))
                   .First();
                scope1.Settings.Load();
                Console.WriteLine("Query:2, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
                sw.Reset();
                sw.Start();
            }
        }
        Console.WriteLine();
        using (CMEntities model = new CMEntities())
        {
            for (int i = 0; i < 5; i++)
            {
                var scope = model.Scopes
                    .Where(s => (s.Level == intLevel && s.Name == name))
                    .First();
                var settings = model.Settings.Where(s => s.Scope.Id == scope.Id).ToList();
                Console.WriteLine("Query:3, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
                sw.Reset();
                sw.Start();
            }                
        }
    }
    }

结果:

Query:1, Iter:0, Time:2477
Query:1, Iter:1, Time:1831
Query:1, Iter:2, Time:1933
Query:1, Iter:3, Time:1774
Query:1, Iter:4, Time:1949

Query:2, Iter:0, Time:2036
Query:2, Iter:1, Time:1870
Query:2, Iter:2, Time:1921
Query:2, Iter:3, Time:1751
Query:2, Iter:4, Time:1758

Query:3, Iter:0, Time:188
Query:3, Iter:1, Time:201
Query:3, Iter:2, Time:185
Query:3, Iter:3, Time:203
Query:3, Iter:4, Time:217

编辑4 :我使用NHibernate重写了代码:

    static void Main(string[] args)
    {

        var cfg = new StoreConfiguration();
        var sessionFactory = Fluently.Configure()
          .Database(MsSqlConfiguration.MsSql2005
              .ConnectionString("Data Source=.;Initial Catalog=CM;Integrated Security=True;MultipleActiveResultSets=True")
          )
          .Mappings(m =>
                m.AutoMappings.Add(
                    AutoMap.AssemblyOf<Entities.Scope>(cfg)
                        .Conventions
                            .Add(
                                Table.Is(x => x.EntityType.Name + "s"),
                                PrimaryKey.Name.Is(x => "Id"),
                                ForeignKey.EndsWith("_id")
                            )
                    )
          )             
          .BuildSessionFactory();
        Stopwatch sw = new Stopwatch();
        for (int i = 0; i < 5; i++)
        {
            sw.Start();
            var session = sessionFactory.OpenSession();
            int intLevel = 2;
            string name = "fb226050-4f92-4fca-9442-f76565b33877";
            var scope = session.CreateCriteria<Entities.Scope>()
                .SetFetchMode("Settings", FetchMode.Eager)
                .Add(Restrictions.Eq("Name", name))
                .Add(Restrictions.Eq("Level", intLevel))                    
                .UniqueResult<Entities.Scope>();
            Console.WriteLine("Query:0, Iter:{0}, Time:{1}", i, sw.ElapsedMilliseconds);
            sw.Reset();
        }
    }

结果是:

Query:0, Iter:0, Time:446
Query:0, Iter:1, Time:223
Query:0, Iter:2, Time:303
Query:0, Iter:3, Time:275
Query:0, Iter:4, Time:284

所以NHibernate形成适当的收集速度比EF快10倍。这真的很难过。

以下是NHibernate生成的查询:

SELECT this_.id            AS id0_1_, 
       this_.name          AS name0_1_, 
       this_.LEVEL         AS level0_1_, 
       settings2_.scope_id AS scope4_3_, 
       settings2_.id       AS id3_, 
       settings2_.id       AS id1_0_, 
       settings2_.TYPE     AS type1_0_, 
       settings2_.VALUE    AS value1_0_, 
       settings2_.scope_id AS scope4_1_0_ 
FROM   scopes this_ 
       LEFT OUTER JOIN settings settings2_ 
         ON this_.id = settings2_.scope_id 
WHERE  this_.name = @p0 
       AND this_.LEVEL = @p1 

4 个答案:

答案 0 :(得分:1)

要检查几件事......

  1. 如何在实体模型中定义范围和设置之间的关系?是否使用了正确的外键?多重性(一对多等)怎么样?
  2. 如果将第一个查询更改为:
  3. ,您的执行时间是否会有所改善

    ConfigModelContainer model = new ConfigModelContainer();
    var scope = model.Scopes.Include("Settings")
                     .First<Scope>(s => s.Level == intLevel && s.Name == name);
    

    我不确定Linq会以不同于您的查询来优化此查询,但可能会看到它是否恰好这样做。

    关于升级到EF 4.0,我不知道如果你的项目在.NET 3.5中是多么可行。然而,当我们的项目使用.NET 3.5 / EF 1时,我从未遇到过这个问题。

答案 1 :(得分:1)

当你说实际的TSQL查询运行得很快时,你是在谈论手工编码的查询吗?

尝试使用SQL事件探查器查看EF 3.5生成的内容。也许这将说明为什么性能会如此不同,并提供一些洞察力,以了解是否以及如何提高第一个查询的性能。

此外,这里有一些博客文章提供了如何在EF 4中改进sql生成的具体示例。即使升级到EF 4不是一种选择,它们也可能提供深思熟虑。

Improvements to the Generated SQL in .NET 4.0 Beta1

Improvements to Generated SQL in .NET 4.0

修改

以下是我用来尝试重现结果的代码。这是使用SQL Server 2008 R2,VS 2010(无SP1)和Entity Framework 4.0。我不得不猜测架构;希望它很接近。

创建表格并填充它们:

set nocount on

create table Scopes
(
    [Id]                int identity primary key,
    [Level]             int,
    [Name]              nvarchar(50),
    [ParentScope_Id]    int foreign key references Scopes(Id)
)
create table Settings
(
    [Id]            int identity primary key,
    [Type]          nvarchar(20),
    [Value]         nvarchar(50),
    [Scope_Id]      int foreign key references Scopes(Id)   
)
go

declare @scopeId int,
        @scopeCount int,
        @settingCount int,
        @value nvarchar(50)

set @scopeCount = 0

while @scopeCount < 10
begin   
    insert into Scopes([Level], [Name]) values(1, 'Scope ' + cast(@scopeCount as nvarchar))
    select @scopeId = @@IDENTITY
    set @settingCount = 0

    while @settingCount < 10000
    begin
        set @value = 'Setting ' + cast(@scopeId as nvarchar) + '.' + cast(@settingCount as nvarchar)
        insert into Settings([Type], [Value], [Scope_Id]) values ('Test', @value, @scopeId)
        set @settingCount = @settingCount + 1
    end

    set @scopeCount = @scopeCount + 1
end

使用控制台应用程序进行测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace so_q5205281
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var context = new EFTestEntities())
            {
                int level = 1;
                string name = "Scope 4";

                ExecQuery1(context, level, name);
                ExecQuery1(context, level, name);
                ExecQuery1(context, level, name);

                ExecQuery2(context, level, name);
                ExecQuery2(context, level, name);
                ExecQuery2(context, level, name);
            }

            Console.ReadLine();
        }

        static void ExecQuery1(EFTestEntities context, int level, string name)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();

            var scope = context.Scopes.Include("Settings")
                .Where(s => s.Level == level && s.Name == name)
                .First();

            int settingsCount = scope.Settings.Count();

            stopwatch.Stop();

            Console.WriteLine("Query 1, scope name: {0}, settings count: {1}, seconds {2}", scope.Name, settingsCount, stopwatch.Elapsed.TotalSeconds);
        }

        static void ExecQuery2(EFTestEntities context, int level, string name)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();

            var scope = context.Scopes
                .Where(s => s.Level == level && s.Name == name)
                .First();

            var settings = context.Settings.Where(s => s.Scope.Id == scope.Id).ToList();

            int settingsCount = scope.Settings.Count();

            stopwatch.Stop();

            Console.WriteLine("Query 2, scope name: {0}, settings count: {1}, seconds {2}", scope.Name, settingsCount, stopwatch.Elapsed.TotalSeconds);
        }
    }
}

使用默认设置创建EF模型,并从数据库更新模型:

enter image description here

从EF发送的第一个查询的SQL:

exec sp_executesql N'SELECT 
[Project2].[Id] AS [Id], 
[Project2].[Level] AS [Level], 
[Project2].[Name] AS [Name], 
[Project2].[ParentScope_Id] AS [ParentScope_Id], 
[Project2].[C1] AS [C1], 
[Project2].[Id1] AS [Id1], 
[Project2].[Type] AS [Type], 
[Project2].[Value] AS [Value], 
[Project2].[Scope_Id] AS [Scope_Id]
FROM ( SELECT 
    [Limit1].[Id] AS [Id], 
    [Limit1].[Level] AS [Level], 
    [Limit1].[Name] AS [Name], 
    [Limit1].[ParentScope_Id] AS [ParentScope_Id], 
    [Extent2].[Id] AS [Id1], 
    [Extent2].[Type] AS [Type], 
    [Extent2].[Value] AS [Value], 
    [Extent2].[Scope_Id] AS [Scope_Id], 
    CASE WHEN ([Extent2].[Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
    FROM   (SELECT TOP (1) 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Level] AS [Level], 
        [Extent1].[Name] AS [Name], 
        [Extent1].[ParentScope_Id] AS [ParentScope_Id]
        FROM [dbo].[Scopes] AS [Extent1]
        WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1) ) AS [Limit1]
    LEFT OUTER JOIN [dbo].[Settings] AS [Extent2] ON [Limit1].[Id] = [Extent2].[Scope_Id]
)  AS [Project2]
ORDER BY [Project2].[Id] ASC, [Project2].[C1] ASC',N'@p__linq__0 int,@p__linq__1 nvarchar(4000)',@p__linq__0=1,@p__linq__1=N'Scope 4'

和第二个查询:

exec sp_executesql N'SELECT TOP (1) 
[Extent1].[Id] AS [Id], 
[Extent1].[Level] AS [Level], 
[Extent1].[Name] AS [Name], 
[Extent1].[ParentScope_Id] AS [ParentScope_Id]
FROM [dbo].[Scopes] AS [Extent1]
WHERE ([Extent1].[Level] = @p__linq__0) AND ([Extent1].[Name] = @p__linq__1)',N'@p__linq__0 int,@p__linq__1 nvarchar(4000)',@p__linq__0=1,@p__linq__1=N'Scope 4'

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[Type] AS [Type], 
[Extent1].[Value] AS [Value], 
[Extent1].[Scope_Id] AS [Scope_Id]
FROM [dbo].[Settings] AS [Extent1]
WHERE [Extent1].[Scope_Id] = @p__linq__0',N'@p__linq__0 int',@p__linq__0=5

和输出:

Query 1, scope name: Scope 4, settings count: 10000, seconds 0.6657546
Query 1, scope name: Scope 4, settings count: 10000, seconds 0.1608498
Query 1, scope name: Scope 4, settings count: 10000, seconds 0.1097625
Query 2, scope name: Scope 4, settings count: 10000, seconds 0.0742593
Query 2, scope name: Scope 4, settings count: 10000, seconds 0.0551458
Query 2, scope name: Scope 4, settings count: 10000, seconds 0.0555465

答案 2 :(得分:0)

您可以打印以下结果吗?

    ConfigModelContainer model = new ConfigModelContainer();
    ObjectQuery<Scope> scope = model.Scopes.Include("Settings")
        .Where(s => (s.Level == intLevel && s.Name == name))
        .First();

    Trace.WriteLine(scope.ToTraceString());

看看它在两种情况下打印的内容。

如果你交换查询的位置,第二个运行更快还是更慢?如果这是程序的开始,那么每当您运行第一个查询时,EF将花费时间来获取元数据,验证元数据并安定下来。如果你说简单查询运行得更快,我认为它的EF初始化时间。

答案 3 :(得分:0)

我会计算两个EF生成的查询来检查它们的确切运行时间。也许你会发现一些不同。

第一个查询是有序(请参阅EF生成的查询),这应该使它比第二个querhy(不<<)慢运行 / strong>已订购)。如果您的设置很大,那么您可能正在查看这种排序的运行时间。

我还观察到,次序运行(第一次除外)比第一次迭代更快。这是预期的,因为后续运行不必实例化所有对象 - 它们都是在第一次运行时创建的。但差别很小,因此您的性能瓶颈显然不是对象实例化。

在尝试第二次查询之前,您还要处理实体上下文。

因此唯一的区别(第一个查询的排序除外)是在第一个查询中,您复制了与“设置”连接的每个记录中“范围”中的四个字段。因此,您有n个重复的四个范围字段,其中n =设置数。由于这四个字段似乎很小,这不应该产生您测量的10倍性能差异,尽管10倍差异仅为2秒。

您对该特定范围行有多少设置?