복붙노트

[SQL] (가) 극적으로 엔티티 프레임 워크의 성능이 열화 () 연산자를 포함하는 이유는 무엇입니까?

SQL

(가) 극적으로 엔티티 프레임 워크의 성능이 열화 () 연산자를 포함하는 이유는 무엇입니까?

UPDATE 3 : 이번 발표에 따르면,이되었습니다는 EF6 알파 2의 EF 팀에 의해 해결.

업데이트 2 :이 문제를 해결하기위한 제안을 만들었습니다. 그것을 위해 투표, 여기.

하나 개의 매우 간단한 테이블과 SQL 데이터베이스를 고려하십시오.

CREATE TABLE Main (Id INT PRIMARY KEY)

나는 만 개 레코드 테이블을 채 웁니다.

WITH Numbers AS
(
  SELECT 1 AS Id
  UNION ALL
  SELECT Id + 1 AS Id FROM Numbers WHERE Id <= 10000
)
INSERT Main (Id)
SELECT Id FROM Numbers
OPTION (MAXRECURSION 0)

나는 테이블에 대한 EF 모델을 구축하고 LINQPad에서 다음 쿼리를 (LINQPad 자동으로 덤프를 생성하지 않도록 나는 "C #을 문"모드를 사용하고 있습니다) 실행합니다.

var rows = 
  Main
  .ToArray();

실행 시간 ~ 0.07 초입니다. 지금은 연산자와 쿼리를 재 실행 포함 추가 할 수 있습니다.

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  Main
  .Where (a => ids.Contains(a.Id))
  .ToArray();

이 경우에 대한 실행 시간은 20.14 초 (288 배 느린)입니다!

내가 절단 및 SQL Server 관리 Studio에 LINQPad의 SQL 창에서 붙여 넣기 시도 있도록 먼저 I에서, 쿼리에 대한 방출되는 T-SQL을 실행하는 데 오래 걸리는 것을 의심.

SET NOCOUNT ON
SET STATISTICS TIME ON
SELECT 
[Extent1].[Id] AS [Id]
FROM [dbo].[Primary] AS [Extent1]
WHERE [Extent1].[Id] IN (1,2,3,4,5,6,7,8,...

그리고 그 결과는 있었다

SQL Server Execution Times:
  CPU time = 0 ms,  elapsed time = 88 ms.

다음으로 LINQPad 문제를 일으키는 의심하지만, 성능은 내가 LINQPad 또는 콘솔 응용 프로그램에서 실행 여부를 동일합니다.

그래서, 문제가 어딘가에 엔티티 프레임 워크 내에서 않은 것 같습니다.

여기 뭔가 잘못을하고 있습니까? 이것은 내가 성능을 향상하기 위해 할 수있는 일이, 내 코드의 시간이 중요한 부분입니다?

내가 엔티티 프레임 워크 4.1 및 SQL Server 2008 R2를 사용하고 있습니다.

UPDATE 1 :

아래의 설명에서 지연이 그것을 다시받은 데이터를 구문 분석하는 동안 EF는 초기 쿼리를 구축하거나하는 동안 발생 여부에 대한 몇 가지 질문이 있었다. 이를 테스트하기 위해 나는 다음 코드를 실행

var ids = Main.Select(a => a.Id).ToArray();
var rows = 
  (ObjectQuery<MainRow>)
  Main
  .Where (a => ids.Contains(a.Id));
var sql = rows.ToTraceString();

힘은 데이터베이스에 대해 그것을 실행하지 않고 쿼리를 생성하는 EF있다. 그 거의 모든 시간의 초기 쿼리를 작성에서 가져온 것입니다 나타나도록 결과는이 코드가 실행될 ~ 20 secords 필요한 것이 었습니다.

다음 구조에 CompiledQuery? 아니 너무 빨리 ... CompiledQuery는 기본 타입 (INT, 문자열, 부동 소수점 등)로 쿼리에 전달 된 매개 변수가 필요합니다. 내가 ID의 목록을 사용할 수 있도록 그것은 배열이나는 IEnumerable을 허용하지 않습니다.

해결법

  1. ==============================

    1.업데이트 : EF6 InExpression에서의 추가로 처리 Enumerable.Contains의 성능이 크게 향상되었다. 이 답변에 설명 된 접근 방식은 더 이상 필요하지 않습니다.

    업데이트 : EF6 InExpression에서의 추가로 처리 Enumerable.Contains의 성능이 크게 향상되었다. 이 답변에 설명 된 접근 방식은 더 이상 필요하지 않습니다.

    당신은 대부분의 시간 쿼리의 번역을 처리 소요되는 것이 옳다. 현재 IN 절을 나타내는 표현을 포함하지 않는 EF의 제공 모델은 따라서 ADO.NET 공급자는 기본적 IN 지원할 수 없습니다. 대신, Enumerable.Contains의 구현은 무엇인가, 즉 트리 OR 표현으로 변환하는 C에서이 같은 같은 # 외모 :

    new []{1, 2, 3, 4}.Contains(i)
    

    ... 우리는 다음과 같이 표현 될 수있는 DbExpression 트리를 생성합니다 :

    ((1 = @i) OR (2 = @i)) OR ((3 = @i) OR (4 = @i))
    

    (표현의 나무는 우리가 하나의 긴 척추를 통해 모든 관찰 보고서가 있다면 표현의 방문자가 스택 오버 플로우를 칠 것이 더 많은 기회 (예, 우리가 실제로 공격 않은 우리의 테스트에서)이있을 것입니다 때문에 균형되어야한다)

    우리는 나중에이 패턴을 인식하고 SQL 생성시 IN 절에 감소 할 수있는 능력을 가질 수있는 ADO.NET 공급자,이 같은 나무를 보낼 수 있습니다.

    우리는 EF4에 Enumerable.Contains에 대한 지원을 추가 할 때, 우리는 공급자 모델 IN 식에 대한 지원을 도입 할 필요없이 그것을하는 것이 바람직하다고 생각하고, 정직하게, 10000 우리는 고객에 전달할 것이라고 예상 요소의 수보다 훨씬 더 Enumerable.Contains. 그게 내가이 짜증 것을 이해하고 표현 나무의 조작은 특정 시나리오의 상황이 너무 비싸 수 있다고 말했다.

    나는 우리의 개발자 중 하나를 사용하여이 문제를 논의하고 우리는 미래에 우리가 먼저 수준의 지원을 추가하여 구현을 바꿀 수 있다고 생각합니다. 나는 확실히 이것이 우리의 백 로그에 추가 할 것입니다,하지만 그것은 우리가 만드는하고 싶은 다른 많은 개선이있다 제공 할 때 약속 할 수 없다.

    이미 나는 다음을 추가 스레드에서 제안 해결하려면 :

    당신이 포함에 합격 요소의 수와 데이터베이스 라운드 트립의 수의 균형을하는 방법을 만드는 것이 좋습니다. 예를 들어, 내 자신의 테스트 그게 컴퓨팅 및 SQL Server의 로컬 인스턴스에 대해 실행 (100 개) 요소와 쿼리가 1/60 초 소요 관찰했다. 당신이 ID의 100 개 가지 세트 100 개 쿼리를 실행하기 만 개 요소와 쿼리에 당신에게 동등한 결과를 줄 것이다 방식으로 쿼리를 쓸 수있는 경우에, 당신은 aproximately 1.67 초 대신 18 초에 결과를 얻을 수 있습니다.

    다른 청크 크기는 쿼리 및 데이터베이스 연결의 대기 시간에 따라 잘 작동합니다. 특정 검색어를 들어, 전달 순서는 중복을 갖는 경우, 즉 또는 Enumerable.Contains가 중첩 된 상태로 사용되는 경우에는 그 결과 중복 요소를 획득 할 수있다.

    여기에 코드 조각 (미안 덩어리로 입력을 슬라이스하는 데 사용되는 코드는 너무 복잡한 보이는 경우.이 같은 일을 달성하는 간단한 방법이 있습니다,하지만 난 보존 시퀀스 스트리밍하는 패턴을 마련하려고 노력하고 아마 그 부분을 :)) 과장 그래서 나는 LINQ에서 같은 것을 찾을 수 없습니다 :

    용법:

    var list = context.GetMainItems(ids).ToList();
    

    컨텍스트 또는 저장소의 방법 :

    public partial class ContainsTestEntities
    {
        public IEnumerable<Main> GetMainItems(IEnumerable<int> ids, int chunkSize = 100)
        {
            foreach (var chunk in ids.Chunk(chunkSize))
            {
                var q = this.MainItems.Where(a => chunk.Contains(a.Id));
                foreach (var item in q)
                {
                    yield return item;
                }
            }
        }
    }
    

    열거 시퀀스 슬라이스에 대한 확장 방법 :

    public static class EnumerableSlicing
    {
    
        private class Status
        {
            public bool EndOfSequence;
        }
    
        private static IEnumerable<T> TakeOnEnumerator<T>(IEnumerator<T> enumerator, int count, 
            Status status)
        {
            while (--count > 0 && (enumerator.MoveNext() || !(status.EndOfSequence = true)))
            {
                yield return enumerator.Current;
            }
        }
    
        public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> items, int chunkSize)
        {
            if (chunkSize < 1)
            {
                throw new ArgumentException("Chunks should not be smaller than 1 element");
            }
            var status = new Status { EndOfSequence = false };
            using (var enumerator = items.GetEnumerator())
            {
                while (!status.EndOfSequence)
                {
                    yield return TakeOnEnumerator(enumerator, chunkSize, status);
                }
            }
        }
    }
    

    도움이 되었기를 바랍니다!

  2. ==============================

    2.(당신은 프리미엄 지원을 경우)이 소요 당신은 아마 성공하지 할 것입니다 때문에 해결에 나이를 지출하지 않는 당신을 위해 차단하는 성능 문제를 발견하고 직접 MS와 통신 할 경우 나이.

    (당신은 프리미엄 지원을 경우)이 소요 당신은 아마 성공하지 할 것입니다 때문에 해결에 나이를 지출하지 않는 당신을 위해 차단하는 성능 문제를 발견하고 직접 MS와 통신 할 경우 나이.

    사용은 해결 및 해결 방법 성능 문제의 경우와 EF 직접 SQL을 의미합니다. 그것에 대해 아무것도 나쁜 있습니다. 더 이상 SQL을 사용하여 EF를 사용하여 = 여부를 거짓말이라고 글로벌 아이디어. 당신은 너무 SQL 서버 2008 R2가 :

    성능이 당신을 위해 중요한 경우 당신은 더 나은 솔루션을 찾을 수 없습니다. 이 절차는 매핑과 현재 버전 중 하나를 테이블 반환 매개 변수 또는 여러 결과 집합을 지원하지 않기 때문에 EF에서 실행할 수 없습니다.

  3. ==============================

    3.우리는 절을 포함하는 중간 테이블을 추가하고 사용하는 데 필요한 것을 LINQ 쿼리에서 해당 테이블에 가입하여 EF이 문제를 포함 해결 할 수 있었다. 우리는이 방법으로 놀라운 결과를 얻을 수 있었다. 우리는 큰 EF 모델을 가지고 "포함"우리가 사용이 절을 "포함"고 쿼리에 대한 매우 빈약 한 성과를 얻고 있었다 EF 쿼리를 미리 컴파일 할 때 사용할 수 없습니다.

    우리는 절을 포함하는 중간 테이블을 추가하고 사용하는 데 필요한 것을 LINQ 쿼리에서 해당 테이블에 가입하여 EF이 문제를 포함 해결 할 수 있었다. 우리는이 방법으로 놀라운 결과를 얻을 수 있었다. 우리는 큰 EF 모델을 가지고 "포함"우리가 사용이 절을 "포함"고 쿼리에 대한 매우 빈약 한 성과를 얻고 있었다 EF 쿼리를 미리 컴파일 할 때 사용할 수 없습니다.

    개요 :

  4. ==============================

    4.내 원래의 대답을 편집 - 당신의 엔티티의 복잡성에 따라, 가능한 해결 방법이 있습니다. 당신이 EF 당신의 실체를 채우기 위해 생성하는 SQL을 알고 있다면, 당신은 직접 DbContext.Database.SqlQuery를 사용하여 실행할 수 있습니다. EF 4에서, 나는 당신이 ObjectContext.ExecuteStoreQuery을 사용할 수 있다고 생각하지만, 나는 그것을 시도하지 않았다.

    내 원래의 대답을 편집 - 당신의 엔티티의 복잡성에 따라, 가능한 해결 방법이 있습니다. 당신이 EF 당신의 실체를 채우기 위해 생성하는 SQL을 알고 있다면, 당신은 직접 DbContext.Database.SqlQuery를 사용하여 실행할 수 있습니다. EF 4에서, 나는 당신이 ObjectContext.ExecuteStoreQuery을 사용할 수 있다고 생각하지만, 나는 그것을 시도하지 않았다.

    예를 들어, 모두 StringBuilder를 사용하여 SQL 문을 생성하기 위해 아래에 내 원래의 대답에서 코드를 사용하여, 나는 다음을 수행 할 수 있었다

    var rows = db.Database.SqlQuery<Main>(sql).ToArray();
    

    및 총 시간은 0.5 초에 약 26초에서 갔다.

    나는 그것이 추한 말을 처음, 잘하면 더 나은 솔루션의 선물 그 자체 일 것이다.

    조금 더 생각 후에, 나는 당신이 당신의 결과를 필터링하는 조인을 사용하는 경우, EF는 ID의 긴 목록 있음을 구축해야하지 않는다는 것을 깨달았다. 이 동시 쿼리의 수에 따라 복잡 할 수 있지만, 나는 당신이 그들을 분리 사용자 ID 또는 세션 ID를 사용할 수 있습니다 생각합니다.

    이를 테스트하기 위해, 내가 주와 같은 스키마를 대상 테이블을 만들었습니다. 나는 그 가장 SQL Server가 단일 INSERT에 동의합니다 이후 1000 일괄 목표 테이블을 채우는 데 INSERT 명령을 생성하기 위해 모두 StringBuilder를 사용했다. 직접 (약 0.3 초 ​​2.5 대 초) EF 통과하는 것보다 훨씬 빨리 SQL 문을 한 실행, 나는 테이블 스키마가 변경되지해야하기 때문에 괜찮을 것이라고 생각합니다.

    마지막으로, 훨씬 더 간단한 쿼리의 결과와 0.5 초 미만으로 실행 조인을 사용하여 선택.

    ExecuteStoreCommand("DELETE Target");
    
    var ids = Main.Select(a => a.Id).ToArray();
    var sb = new StringBuilder();
    
    for (int i = 0; i < 10; i++)
    {
        sb.Append("INSERT INTO Target(Id) VALUES (");
        for (int j = 1; j <= 1000; j++)
        {
            if (j > 1)
            {
                sb.Append(",(");
            }
            sb.Append(i * 1000 + j);
            sb.Append(")");
        }
        ExecuteStoreCommand(sb.ToString());
        sb.Clear();
    }
    
    var rows = (from m in Main
                join t in Target on m.Id equals t.Id
                select m).ToArray();
    
    rows.Length.Dump();
    

    그리고위한 EF에 의해 생성 된 SQL은 조인 :

    SELECT 
    [Extent1].[Id] AS [Id]
    FROM  [dbo].[Main] AS [Extent1]
    INNER JOIN [dbo].[Target] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Id]
    

    (원래의 대답)

    이 답변 아니지만, 몇 가지 추가 정보를 공유하고 싶어하고 코멘트에 맞게 너무 깁니다. 나는 당신의 결과를 재현하고, 추가 할 수있는 몇 가지 다른 일을 할 수 있었다 :

    SQL 프로필러 I 문제가 발생 및 그 크기 (48,980 바이트)으로 쿼리를 전송했다 의심되도록 지연이 최초 쿼리 (Main.Select) 및 제 Main.Where 쿼리의 실행 사이이다.

    그러나 T-SQL에서 같은 SQL 문을 구축 동적으로 쓰기에 시간을 포함하여 사용자들은 일초보다 적게 소요되며, 0.112 초 걸렸 같은 SQL 문을 구축하고있는 SqlCommand를 사용하여 실행, 당신의 Main.Select 문에서 ID를 복용하고, 콘솔에 내용을 표시합니다.

    이 시점에서, 나는 EF는 쿼리를 구축으로 만 식별자의 각 처리 / 어떤 분석을하고 있다고 생각한다. 나는 확실한 대답과 해결책을 :( 제공 할 수 바랍니다.

    여기에 내가 (내가 휴가 작업하려고 서두에있어, 너무 가혹하게 비판하지 마십시오) SSMS 및 LINQPad에 노력 코드는 다음과 같습니다

    declare @sql nvarchar(max)
    
    set @sql = 'SELECT 
    [Extent1].[Id] AS [Id]
    FROM [dbo].[Main] AS [Extent1]
    WHERE [Extent1].[Id] IN ('
    
    declare @count int = 0
    while @count < 10000
    begin
        if @count > 0 set @sql = @sql + ','
        set @count = @count + 1
        set @sql = @sql + cast(@count as nvarchar)
    end
    set @sql = @sql + ')'
    
    exec(@sql)
    
    var ids = Mains.Select(a => a.Id).ToArray();
    
    var sb = new StringBuilder();
    sb.Append("SELECT [Extent1].[Id] AS [Id] FROM [dbo].[Main] AS [Extent1] WHERE [Extent1].[Id] IN (");
    for(int i = 0; i < ids.Length; i++)
    {
        if (i > 0) 
            sb.Append(",");     
        sb.Append(ids[i].ToString());
    }
    sb.Append(")");
    
    using (SqlConnection connection = new SqlConnection("server = localhost;database = Test;integrated security = true"))
    using (SqlCommand command = connection.CreateCommand())
    {
        command.CommandText = sb.ToString();
        connection.Open();
        using(SqlDataReader reader = command.ExecuteReader())
        {
            while(reader.Read())
            {
                Console.WriteLine(reader.GetInt32(0));
            }
        }
    }
    
  5. ==============================

    5.내가 엔티티 프레임 워크에 익숙하지 않아요하지만 당신은 다음을 수행하는 경우 반환 한 더 나은 무엇입니까?

    내가 엔티티 프레임 워크에 익숙하지 않아요하지만 당신은 다음을 수행하는 경우 반환 한 더 나은 무엇입니까?

    대신이의는 :

    var ids = Main.Select(a => a.Id).ToArray();
    var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();
    

    이 방법에 대해 (ID를 가정하면 INT이다)

    var ids = new HashSet<int>(Main.Select(a => a.Id));
    var rows = Main.Where (a => ids.Contains(a.Id)).ToArray();
    
  6. ==============================

    6.http://entityframework.codeplex.com/SourceControl/changeset/a7b70f69e551 : 그것은 엔티티 프레임 워크 (6) 알파 2에 고정

    http://entityframework.codeplex.com/SourceControl/changeset/a7b70f69e551 : 그것은 엔티티 프레임 워크 (6) 알파 2에 고정

    http://blogs.msdn.com/b/adonet/archive/2012/12/10/ef6-alpha-2-available-on-nuget.aspx

  7. ==============================

    7.에가 포함 된 캐시 대안?

    에가 포함 된 캐시 대안?

    내가 엔티티 프레임 워크 기능 제안 링크에 내 두 펜스를 추가했습니다, 그래서 이것은 단지 나에게 물었다.

    SQL을 생성 할 때 문제는 확실히이다. 나는 쿼리 생성 4 초였다 데이터 누구의 클라이언트를 가지고 있지만 실행은 0.1 초였다.

    나는 동적 LINQ 및 관찰 보고서를 사용할 때 SQL 세대와 마찬가지로 오래 걸려있는 것을 발견하지만 캐시 할 수있는 일이 생성됩니다. 다시 실행할 때 그래서 0.2 초 아래로했다.

    는 SQL에서 여전히 생성합니다.

    초기 히트를 위장 할 수 있는지 고려해야 할 다른 뭔가가, 배열의 수는 훨씬 변경하고 쿼리를 많이 실행되지 않습니다. (LINQ 패드에서 테스트)

  8. ==============================

    8.문제는 엔티티 프레임 워크 SQL의 세대입니다. 매개 변수 중 하나가리스트 인 경우는 쿼리를 캐시 할 수 있습니다.

    문제는 엔티티 프레임 워크 SQL의 세대입니다. 매개 변수 중 하나가리스트 인 경우는 쿼리를 캐시 할 수 있습니다.

    EF는 쿼리 당신이 문자열 목록을 변환하고 문자열에 .Contains을 할 수있는 캐시에 도착합니다.

    그래서 예를 들어,이 코드는 쿼리를 캐시 할 수 훨씬 더 빨리 EF 이후 실행됩니다 :

    var ids = Main.Select(a => a.Id).ToArray();
    var idsString = "|" + String.Join("|", ids) + "|";
    var rows = Main
    .Where (a => idsString.Contains("|" + a.Id + "|"))
    .ToArray();
    

    이 쿼리가 생성 될 때 가능성은 당신의 C #을 빠르게하지만 그것은 잠재적으로 SQL을 늦출 수 있도록에서 대신 등을 이용하여 생성됩니다. 내 경우에는 내가 내 SQL 실행의 모든 ​​성능이 저하 될하지 않았고, C #을 훨씬 더 빨리 달렸다.

  9. from https://stackoverflow.com/questions/7897630/why-does-the-contains-operator-degrade-entity-frameworks-performance-so-drama by cc-by-sa and MIT license