18

Tôi đang gặp sự cố về hiệu suất thú vị với Entity Framework. Tôi đang sử dụng Code First.Vấn đề về hiệu suất khung thực thể

Đây là cấu trúc của các thực thể của tôi:

Một cuốn sách có thể có nhiều đánh giá. Bài đánh giá được liên kết với một cuốn sách. Bài đánh giá có thể có một hoặc nhiều Nhận xét. Nhận xét được liên kết với một đánh giá.

public class Book 
{ 
    public int BookId { get; set; } 
    // ... 
    public ICollection<Review> Reviews { get; set; } 
} 

public class Review 
{ 
    public int ReviewId { get; set; } 
    public int BookId { get; set; } 
    public Book Book { get; set; } 
    public ICollection<Comment> Comments { get; set; } 
} 

public class Comment 
{ 
    public int CommentId { get; set; } 
    public int ReviewId { get; set; } 
    public Review Review { get; set; } 
} 

Tôi đã tạo cơ sở dữ liệu với nhiều dữ liệu và thêm chỉ mục thích hợp. Tôi đang cố tải xuống một cuốn sách có 10.000 bài đánh giá về nó bằng cách sử dụng truy vấn này:

var bookAndReviews = db.Books.Where(b => b.BookId == id) 
         .Include(b => b.Reviews) 
         .FirstOrDefault(); 

Cuốn sách cụ thể này có 10.000 bài đánh giá. Hiệu suất của truy vấn này là khoảng 4 giây. Chạy chính xác cùng một truy vấn (thông qua SQL Profiler) thực sự trả về trong thời gian không có ở tất cả. Tôi đã sử dụng cùng một truy vấn và một SqlDataAdapter và các đối tượng tùy chỉnh để lấy dữ liệu và nó xảy ra dưới 500 mili giây.

Sử dụng ANTS Performance Profiler nó trông giống như một số lượng lớn thời gian đang được chi tiêu làm một vài điều khác nhau:

Các Bằng phương pháp đang được gọi là 50 triệu lần.

Có ai biết tại sao cần gọi 50 triệu lần này và làm cách nào để tăng hiệu suất cho việc này?

+0

Bạn có thực sự muốn xem truy vấn nào đang được tạo bởi câu lệnh của bạn hoặc bạn cho rằng đó là truy vấn tối ưu? –

+1

Hãy thử EF Profiler. –

+1

Vấn đề không phải là truy vấn như tôi đã nêu. Tôi lấy truy vấn chính xác mà EF đang tạo ra và sử dụng nó trong một Sql Data Adapter bằng cách sử dụng ADO.net thông thường, tải lên các đối tượng tương tự bằng tay. Nó chạy trong chưa đầy một giây. – Dismissile

Trả lời

20

Tại sao mức bằng 50 triệu lần?

Nghe có vẻ khá đáng ngờ. Bạn có 10.000 đánh giá và 50.000.000 cuộc gọi đến Equals. Giả sử rằng điều này là do bản đồ nhận dạng được thực hiện bởi EF. Bản đồ nhận dạng đảm bảo rằng mỗi thực thể có khóa duy nhất được theo dõi bởi ngữ cảnh chỉ một lần vì vậy nếu bối cảnh đã có cá thể có cùng khóa với bản ghi được nạp từ cơ sở dữ liệu thì nó sẽ không hiện thực hóa cá thể mới và thay vào đó sử dụng phiên bản hiện có. Bây giờ làm thế nào điều này có thể trùng với những con số? đoán đáng sợ của tôi:

============================================= 
1st  record read | 0  comparisons 
2nd  record read | 1  comparison 
3rd  record read | 2  comparisons 
... 
10.000th record read | 9.999 comparisons 

đó có nghĩa là mỗi bản ghi mới được so sánh với tất cả các kỷ lục tồn tại trong bản đồ sắc. Bằng cách áp dụng toán học để tính tổng của tất cả so sánh chúng ta có thể sử dụng một cái gì đó gọi là "Arithmetic chuỗi":

a(n) = a(n-1) + 1 
Sum(n) = (n/2) * (a(1) + a(n)) 
Sum(10.000) = 5.000 * (0 + 9.999) => 5.000 * 10.000 = 50.000.000 

Tôi hy vọng tôi đã không phạm sai lầm trong các giả định hay tính toán của tôi. Chờ đợi! Tôi hy vọng tôi đã nhầm lẫn vì điều này dường như không tốt.

Thử tắt theo dõi thay đổi = hy vọng tắt kiểm tra bản đồ nhận dạng.

Điều này có thể phức tạp.Bắt đầu với:

var bookAndReviews = db.Books.Where(b => b.BookId == id) 
          .Include(b => b.Reviews) 
          .AsNoTracking() 
          .FirstOrDefault(); 

Nhưng có nhiều khả năng tài sản điều hướng của bạn sẽ không được điền (vì nó được xử lý bởi theo dõi thay đổi). Trong trường hợp này, hãy sử dụng phương pháp này:

var book = db.Books.Where(b => b.BookId == id).AsNoTracking().FirstOrDefault(); 
book.Reviews = db.Reviews.Where(r => r.BookId == id).AsNoTracking().ToList(); 

Dù sao bạn có thể xem loại đối tượng nào được chuyển đến Bằng? Tôi nghĩ rằng nó chỉ nên so sánh các khóa chính và thậm chí so sánh 50 triệu nguyên không phải là vấn đề như vậy.

Là một lưu ý phụ, EF rất chậm - thực tế đã được biết đến. Nó cũng sử dụng sự phản chiếu nội bộ khi thực hiện các thực thể vì vậy chỉ cần 10.000 bản ghi có thể mất một thời gian. Trừ khi bạn đã làm điều đó, bạn cũng có thể tắt tạo proxy động (db.Configuration.ProxyCreationEnabled).

+0

Phân tích tuyệt vời! Theo các bài kiểm tra (thực thể đơn giản không có thuộc tính nav.) Tôi đã thực hiện một số thời gian trước đây, 'AsNoTracking' giảm thời gian thực hiện xuống còn 50%. Tôi có thể tưởng tượng rằng việc tạo ảnh chụp nhanh cho các thực thể được nạp như được theo dõi là đắt hơn so với việc gọi 'Equals' trong bản đồ nhận dạng. Nếu bạn gọi cùng một truy vấn lần thứ hai (cả hai đều được theo dõi) trong cùng một ngữ cảnh, nó sẽ trả về nhanh (ít hơn 1/10 của cuộc gọi đầu tiên), nhanh hơn nhiều so với việc tải không theo dõi - cho phép tôi đoán rằng kiểm tra 'Bằng trong bản đồ nhận dạng tương đối rẻ. – Slauma

+0

BTW: 'Include' cũng hoạt động với' AsNoTracking() ', bộ sưu tập điều hướng được phổ biến. (Hoặc bạn có nghĩa là thuộc tính điều hướng ngược 'Review.Book' sẽ không được điền?) – Slauma

1

Tôi biết điều này nghe có vẻ khập khiễng, nhưng bạn đã thử các cách khác xung quanh, ví dụ:

var reviewsAndBooks = db.Reviews.Where(r => r.Book.BookId == id) 
         .Include(r => r.Book); 

tôi đã nhận thấy hiệu suất đôi khi tốt hơn từ EF khi bạn tiếp cận các truy vấn của bạn theo cách này (nhưng tôi đã không có thời gian để tìm ra lý do tại sao).

+0

Tôi cá nhân sẽ tránh điều này do sự cố với deadlocks. – Skarsnik

Các vấn đề liên quan