18

Entity Framework에서 흥미로운 성능 문제가 있습니다. 나는 Code First를 사용하고있다.Entity Framework 성능 문제

책이 많은 리뷰를 가질 수 있습니다 : 여기

내 기관의 구조입니다. 검토는 하나의 장부와 연관되어 있습니다. 검토는 하나 이상의 의견을 가질 수 있습니다. 댓글은 하나의 리뷰와 연결됩니다.

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; } 
} 

데이터를 많이 채우고 적절한 인덱스를 추가했습니다. 이 쿼리를 사용하여 10,000 개의 리뷰가있는 책 한 권을 검색하려고합니다.

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

이 특정 책은 10,000 개의 리뷰가 있습니다. 이 쿼리의 성능은 약 4 초입니다. SQL Profiler를 통해 똑같은 쿼리를 실행하면 시간이 전혀 걸리지 않습니다. 같은 쿼리와 SqlDataAdapter 및 사용자 지정 개체를 사용하여 데이터를 검색했으며 500 밀리 초 미만으로 발생했습니다.

개미 성능 프로파일 러를 사용하여 몇 가지 다른 일을 소요되는 시간의 대량 같습니다

이 방법은 5000 만 번 호출되고 같음.

누구에게도이 5 천만 번을 전화해야하는 이유와 내가이 성능을 향상시킬 수있는 방법을 알고 있습니까?

+0

실제로 쿼리가 생성 한 쿼리를 보았습니까? 아니면 최적의 쿼리라고 가정하고 있습니까? –

+1

EF 프로파일 러를 사용해보십시오. –

+1

문제는 내가 설명한 것처럼 아닙니다. EF가 생성하는 정확한 쿼리를 가져 와서 같은 ADO.net을 사용하는 SQL 데이터 어댑터에서 사용했습니다. 동일한 개체를 수동으로로드했습니다. 그것은 1 초 이내에 실행됩니다. – Dismissile

답변

20

왜 50M이라고하는 이유는 무엇입니까?

상당히 의심스러운 것 같습니다. Equals에 10.000 건의 리뷰와 50.000.000 건의 전화가 있습니다. 이것은 EF가 내부적으로 구현 한 ID 맵에 의해 발생한다고 가정합니다. 아이덴티티 맵은 유일한 키를 가진 각 엔티티가 컨텍스트에 의해 한 번만 추적되도록합니다. 따라서 컨텍스트에 이미 데이터베이스의로드 된 레코드와 동일한 키가있는 인스턴스가 있으면 새 인스턴스를 구체화하지 않고 기존 인스턴스를 사용합니다. 이제 어떻게 이것이 그 숫자와 일치 할 수 있습니까? 내 무서운 생각 엔 :

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

각각의 새로운 기록 신원지도의 모든 기존 레코드와 비교되는 것을 의미한다. 수학을 적용하는 것은 우리가 "산술 순서"라는 것을 사용할 수있는 모든 비교의 합을 계산함으로써 :

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 

을 나는 내 가정이나 계산에 실수를하지 않았다 바랍니다. 기다림! 나는 이것이 좋게 보이지 않기 때문에 실수를하기를 바란다.

변경 추적을 사용 중지 해보세요. 신원지도 확인을 사용하지 않기를 바랍니다.

힘들 수 있습니다.

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

을하지만 (이를 변경 추적에 의해 처리되기 때문에) 탐색 속성이 채워지지 않습니다 것을 큰 기회가있다 :로 시작합니다. 이 경우 다음과 같은 접근 방식을 사용하십시오.

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

어쨌든 Equals로 전달되는 객체 유형을 확인할 수 있습니까? 나는 그것이 기본 키만을 비교해야하고 심지어 50M 정수 비교가 그러한 문제가되어서는 안된다고 생각한다.

부수적으로 EF는 느리다. 잘 알려진 사실이다. 엔티티를 구체화 할 때 내부적으로 리플렉션을 사용하므로 간단히 10.000 개의 레코드가 "언젠가"걸릴 수 있습니다. 이미 수행 한 경우가 아니면 동적 프록시 생성을 해제 할 수도 있습니다 (db.Configuration.ProxyCreationEnabled).

+0

굉장한 분석! 테스트 (네비게이션 속성이없는 간단한 엔티티)에 따르면 얼마 전에 작성한 'AsNoTracking'은 materialization 시간을 50 %로 줄였습니다. 추적 된 엔티티에 대한 스냅 샷 생성은 ID 맵에서 'Equals'를 호출하는 것보다 비용이 많이 들지만 상상할 수 있습니다. 같은 문맥에서 같은 쿼리를 두 번 호출하면 (추적 된 것과 같이) 첫 번째 호출의 1/10보다 빠르며, 추적하지 않고로드하는 것보다 훨씬 빠릅니다. 따라서 'Equals' 확인이 가능합니다. 아이덴티티 맵에서 상대적으로 저렴합니다. – Slauma

+0

BTW :'Include'는'AsNoTracking()'과 함께 작동하며, 네비게이션 컬렉션이 채워집니다. (또는 역 탐색 속성 인'Review.Book'이 채워지지 않는다는 것을 의미 했습니까?) – Slauma

1

예를 들어,이 절름발이 소리로 들리 겠지만,하지만 당신은 주위에 다른 방법을 시도 :이 방법으로 쿼리를 접근 할 때 내가 EF에서 때때로 더 나은 성능을 발견했습니다 (하지만 난 없었

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

그 이유를 알아낼 시간).

+0

이 문제로 인해이 문제를 직접 피할 수 있습니다. 교착 상태. – Skarsnik