2012-10-15 23 views
13

Tôi đã tìm kiếm rất nhiều vấn đề về hiệu năng và thử nhiều thứ khác nhau, nhưng tôi không thể làm cho nó hoạt động đủ nhanh. Đây là vấn đề của tôi với hình thức đơn giản nhất:CodeFirst loading 1 parent được liên kết với 25 000 trẻ em chậm

Tôi đang sử dụng khung thực thể 5 và tôi muốn có thể tải các phiên bản con lười của cha mẹ khi người dùng chọn phụ huynh đó, vì vậy tôi không phải kéo toàn bộ cơ sở dữ liệu. Tuy nhiên tôi đã gặp vấn đề về hiệu suất với việc tải trẻ em xuống. Tôi nghĩ rằng vấn đề là dây lên của các tài sản chuyển hướng giữa phụ huynh và trẻ em. Tôi cũng nghĩ rằng nó phải là một cái gì đó tôi đã làm sai vì tôi tin rằng đây là một trường hợp đơn giản.

Vì vậy, tôi đã thiết lập một chương trình để kiểm tra một tải đơn lẻ để cô lập sự cố.

Đây là bài kiểm tra:

Tôi đã tạo một lớp học POCO dành cho cha mẹ và một Lớp học trẻ em POCO. Phụ huynh có n Trẻ em và Trẻ em có 1 Phụ huynh. Chỉ có 1 phụ huynh trong cơ sở dữ liệu SQL Server và 25 000 trẻ em cho cha mẹ đơn đó. Tôi đã thử các phương pháp khác nhau để tải dữ liệu này. Bất cứ khi nào tôi tải một trong hai trẻ em và phụ huynh trong cùng một DbContext, phải mất một thời gian rất dài. Nhưng nếu tôi tải chúng trong DbContexts khác nhau, nó tải rất nhanh. Tuy nhiên, tôi muốn những trường hợp đó trong cùng một DbContext.

Đây là thiết lập thử nghiệm của tôi và tất cả mọi thứ bạn cần để tái tạo nó:

POCOs:

public class Parent 
{ 
    public int ParentId { get; set; } 

    public string Name { get; set; } 

    public virtual List<Child> Childs { get; set; } 
} 

public class Child 
{ 
    public int ChildId { get; set; } 

    public int ParentId { get; set; } 

    public string Name { get; set; } 

    public virtual Parent Parent { get; set; } 
} 

DbContext:

public class Entities : DbContext 
{ 
    public DbSet<Parent> Parents { get; set; } 

    public DbSet<Child> Childs { get; set; } 
} 

TSQL Script để tạo ra the Datab ase và dữ liệu:

USE [master] 
GO 

IF EXISTS(SELECT name FROM sys.databases 
    WHERE name = 'PerformanceParentChild') 
    alter database [PerformanceParentChild] set single_user with rollback immediate 
    DROP DATABASE [PerformanceParentChild] 
GO 

CREATE DATABASE [PerformanceParentChild] 
GO 
USE [PerformanceParentChild] 
GO 
BEGIN TRAN T1; 
SET NOCOUNT ON 

CREATE TABLE [dbo].[Parents] 
(
    [ParentId] [int] CONSTRAINT PK_Parents PRIMARY KEY, 
    [Name] [nvarchar](200) NULL 
) 
GO 

CREATE TABLE [dbo].[Children] 
(
    [ChildId] [int] CONSTRAINT PK_Children PRIMARY KEY, 
    [ParentId] [int] NOT NULL, 
    [Name] [nvarchar](200) NULL 
) 
GO 

INSERT INTO Parents (ParentId, Name) 
VALUES (1, 'Parent') 

DECLARE @nbChildren int; 
DECLARE @childId int; 

SET @nbChildren = 25000; 
SET @childId = 0; 

WHILE @childId < @nbChildren 
BEGIN 
    SET @childId = @childId + 1; 
    INSERT INTO [dbo].[Children] (ChildId, ParentId, Name) 
    VALUES (@childId, 1, 'Child #' + convert(nvarchar(5), @childId)) 
END 

CREATE NONCLUSTERED INDEX [IX_ParentId] ON [dbo].[Children] 
(
    [ParentId] ASC 
) 
GO 

ALTER TABLE [dbo].[Children] ADD CONSTRAINT [FK_Children.Parents_ParentId] FOREIGN KEY([ParentId]) 
REFERENCES [dbo].[Parents] ([ParentId]) 
GO 

COMMIT TRAN T1; 

App.cấu hình có chứa các chuỗi kết nối:

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <connectionStrings> 
    <add 
     name="Entities" 
     providerName="System.Data.SqlClient" 
     connectionString="Server=localhost;Database=PerformanceParentChild;Trusted_Connection=true;"/> 
    </connectionStrings> 
</configuration> 

thử nghiệm giao diện điều khiển lớp:

class Program 
{ 
    static void Main(string[] args) 
    { 
     List<Parent> parents; 
     List<Child> children; 

     Entities entities; 
     DateTime before; 
     TimeSpan childrenLoadElapsed; 
     TimeSpan parentLoadElapsed; 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load only the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      children = entities.Childs.ToList(); 
      childrenLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load only the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 

      before = DateTime.Now; 
      children = entities.Childs.ToList(); 
      childrenLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds" + 
               ", then load the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      children = entities.Childs.ToList(); 
      childrenLoadElapsed = DateTime.Now - before; 

      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 


      System.Diagnostics.Debug.WriteLine("Load the children from DbSet:" + childrenLoadElapsed.TotalSeconds + " seconds" + 
               ", then load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.ToList(); 
      parentLoadElapsed = DateTime.Now - before; 

      before = DateTime.Now; 
      children = parents[0].Childs; 
      childrenLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet:" + parentLoadElapsed.TotalSeconds + " seconds" + 
               ", then load the children from Parent's lazy loaded navigation property:" + childrenLoadElapsed.TotalSeconds + " seconds"); 
     } 

     using (entities = new Entities()) 
     { 
      before = DateTime.Now; 
      parents = entities.Parents.Include(p => p.Childs).ToList(); 
      parentLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet and children from include:" + parentLoadElapsed.TotalSeconds + " seconds"); 

     } 

     using (entities = new Entities()) 
     { 
      entities.Configuration.ProxyCreationEnabled = false; 
      entities.Configuration.AutoDetectChangesEnabled = false; 
      entities.Configuration.LazyLoadingEnabled = false; 
      entities.Configuration.ValidateOnSaveEnabled = false; 

      before = DateTime.Now; 
      parents = entities.Parents.Include(p => p.Childs).ToList(); 
      parentLoadElapsed = DateTime.Now - before; 
      System.Diagnostics.Debug.WriteLine("Load the parent from DbSet and children from include:" + parentLoadElapsed.TotalSeconds + " seconds with everything turned off"); 

     } 

    } 
} 

Dưới đây là kết quả của những thử nghiệm:

tải chỉ phụ huynh từ DbSet: 0.972 giây

Chỉ tải trẻ em từ DbSet: 0,714 giây onds

tải phụ huynh từ DbSet: 0.001 giây, sau đó tải các trẻ em từ DbSet: 8,6026 giây

tải những đứa trẻ từ DbSet: 0,6864 giây, sau đó tải phụ huynh từ DbSet: 7,5816159 giây

tải phụ huynh từ DbSet: 0 giây, sau đó tải các trẻ em từ bất động sản chuyển hướng lười nạp của phụ huynh: 8,5644549 giây

tải phụ huynh từ DbSet và trẻ em từ bao gồm: 8,6428788 giây

Loa d phụ huynh từ DbSet và trẻ em từ bao gồm: 9,1416586 seconds với tất cả mọi thứ tắt

Phân tích

Bất cứ khi nào phụ huynh và các em đang trong DbContext cùng, phải mất một thời gian dài (9 giây) để kết nối mọi thứ. Tôi thậm chí đã cố gắng tắt tất cả mọi thứ từ tạo proxy để tải chậm, nhưng vô ích. Ai đó có thể vui lòng giúp tôi ?

+0

+1: Câu hỏi và phân tích tuyệt vời! – Slauma

Trả lời

5

Tôi đã trả lời similar question trước đó. Câu trả lời trước của tôi chứa lý thuyết trả lời vấn đề này nhưng với câu hỏi chi tiết của bạn, tôi có thể trực tiếp chỉ ra vấn đề ở đâu. Đầu tiên cho phép chạy một trong các trường hợp có vấn đề với profiler hiệu suất. Đây là kết quả từ dotTrace khi sử dụng chế độ truy tìm:

enter image description here

quan hệ Sửa chạy trong vòng lặp. Nó có nghĩa là cho 25.000 hồ sơ bạn có 25.000 lặp nhưng mỗi lần lặp trong nội bộ gọi CheckIfNavigationPropertyContainsEntity trên EntityCollection:

internal override bool CheckIfNavigationPropertyContainsEntity(IEntityWrapper wrapper) 
{ 
    if (base.TargetAccessor.HasProperty) 
    { 
     object navigationPropertyValue = base.WrappedOwner.GetNavigationPropertyValue(this); 
     if (navigationPropertyValue != null) 
     { 
      if (!(navigationPropertyValue is IEnumerable)) 
      { 
       throw new EntityException(Strings.ObjectStateEntry_UnableToEnumerateCollection(base.TargetAccessor.PropertyName, base.WrappedOwner.Entity.GetType().FullName)); 
      } 
      foreach (object obj3 in navigationPropertyValue as IEnumerable) 
      { 
       if (object.Equals(obj3, wrapper.Entity)) 
       { 
        return true; 
       } 
      } 
     } 
    } 
    return false; 
} 

Số lần lặp của vòng lặp bên trong phát triển như các mặt hàng được thêm vào bất động sản chuyển hướng. Toán học nằm trong câu trả lời trước của tôi - đó là chuỗi số học trong đó tổng số lần lặp của vòng lặp bên trong là 1/2 * (n^2 - n) => n^2 phức tạp. Vòng lặp bên trong bên trong vòng lặp ngoài dẫn đến 312.487.500 lần lặp trong trường hợp của bạn cũng như hiển thị theo dõi hiệu suất.

Tôi đã tạo work item on EF CodePlex cho sự cố này.

+0

Đó là một câu trả lời tuyệt vời, cảm ơn rất nhiều. Tôi cũng biết ơn bạn đã tạo một mục công việc trên EF CodePlex. – CurlyFire

5

Đây không phải là câu trả lời vì tôi không có giải pháp để cải thiện hiệu suất, nhưng phần nhận xét không có đủ dung lượng cho các mục sau. Tôi chỉ muốn thêm một vài bài kiểm tra và quan sát bổ sung.

Trước tiên, tôi có thể sao chép thời gian đã đo gần như chính xác cho tất cả bảy thử nghiệm. Tôi đã sử dụng EF 4.1 cho bài kiểm tra.

Một số điều thú vị cần lưu ý:

  • Từ (nhanh) kiểm tra 2 Tôi sẽ kết luận rằng đối tượng thể hóa (chuyển đổi các hàng và cột trở về từ máy chủ cơ sở dữ liệu thành các đối tượng) là không chậm.

  • này cũng được khẳng định bằng cách tải các đối tượng trong thử nghiệm 3 mà không theo dõi sự thay đổi:

    parents = entities.Parents.AsNoTracking().ToList(); 
    // ... 
    children = entities.Childs.AsNoTracking().ToList(); 
    

    Mã này chạy nhanh mặc dù 25.001 đối tượng phải được cụ thể hóa cũng như (nhưng không có mối quan hệ giữa các thuộc tính chuyển hướng sẽ thành lập!).

  • Ngoài thử nghiệm nhanh (2), tôi sẽ kết luận rằng việc tạo ảnh chụp nhanh thực thể để theo dõi thay đổi không chậm.

  • Trong thử nghiệm 3 và 4 mối quan hệ giữa phụ huynh và 25000 trẻ em được cố định khi thực thể được tải từ cơ sở dữ liệu, tức làEF thêm tất cả các thực thể Child vào bộ sưu tập Childs của phụ huynh và đặt Parent trong mỗi đứa trẻ cho cha mẹ được tải. Dường như bước này chậm, như bạn đã đoán:

    Tôi nghĩ rằng vấn đề là dây dẫn của thuộc tính điều hướng giữa cha mẹ và con cái.

    Đặc biệt là bên bộ sưu tập của các mối quan hệ có vẻ là vấn đề: Nếu bạn nhận xét ra tài sản Childs chuyển hướng trong lớp Parent (mối quan hệ vẫn là một mối quan hệ đòi hỏi một-nhiều rồi) thử nghiệm 3 và 4 nhanh, mặc dù EF vẫn đặt thuộc tính Parent cho tất cả 25000 Child thực thể.

    Tôi không biết tại sao việc lấp đầy bộ sưu tập điều hướng trong quá trình sửa lỗi mối quan hệ quá chậm. Nếu bạn mô phỏng nó theo cách thủ công một cách ngây thơ, như vậy ...

    entities.Configuration.ProxyCreationEnabled = false; 
    
    children = entities.Childs.AsNoTracking().ToList(); 
    parents = entities.Parents.AsNoTracking().ToList(); 
    
    parents[0].Childs = new List<Child>(); 
    foreach (var c in children) 
    { 
        if (c.ParentId == parents[0].ParentId) 
        { 
         c.Parent = parents[0]; 
         parents[0].Childs.Add(c); 
        } 
    } 
    

    ... nó chạy nhanh. Rõ ràng mối quan hệ fixup nội bộ không hoạt động theo cách đơn giản này. Có thể cần kiểm tra xem bộ sưu tập đã có chứa con để được kiểm tra:

    foreach (var c in children) 
    { 
        if (c.ParentId == parents[0].ParentId) 
        { 
         c.Parent = parents[0]; 
         if (!parents[0].Childs.Contains(c)) 
          parents[0].Childs.Add(c); 
        } 
    } 
    

    Điều này chậm hơn đáng kể (khoảng 4 giây).

Dù sao, sửa lỗi mối quan hệ dường như là hiệu suất botteneck. Tôi không biết làm thế nào để cải thiện nó nếu bạn cần theo dõi thay đổi và quan hệ chính xác giữa các thực thể đính kèm của bạn.

+0

Cảm ơn đã cho tôi một tay! Tôi sẽ chỉnh sửa bài đăng với nhận xét của bạn và phân tích bổ sung. Tôi cũng sẽ định dạng phân tích của tôi như bạn đã làm, nó là rất rõ ràng hơn những gì thử nghiệm đã được sử dụng để suy ra một điểm. – CurlyFire

+0

Tôi cũng đưa ra một bài kiểm tra khác trong khi tôi đang chờ sự giúp đỡ. Tôi tạo ra một lớp truy cập dữ liệu NHibernate sử dụng cùng một POCO và mã kiểm tra như thế này (ngoại trừ 2 cuối), và mỗi thử nghiệm chạy dưới dấu 1 giây. – CurlyFire

+0

@CurlyFire: Không NHibernate fixup các mối quan hệ tự động? Ví dụ, trong thử nghiệm 3, NH có đặt 'Child.Parent' và' Parent.Childs' cho tất cả các thực thể được nạp vào đúng đối tượng không? – Slauma