[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.업데이트 : 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.(당신은 프리미엄 지원을 경우)이 소요 당신은 아마 성공하지 할 것입니다 때문에 해결에 나이를 지출하지 않는 당신을 위해 차단하는 성능 문제를 발견하고 직접 MS와 통신 할 경우 나이.
(당신은 프리미엄 지원을 경우)이 소요 당신은 아마 성공하지 할 것입니다 때문에 해결에 나이를 지출하지 않는 당신을 위해 차단하는 성능 문제를 발견하고 직접 MS와 통신 할 경우 나이.
사용은 해결 및 해결 방법 성능 문제의 경우와 EF 직접 SQL을 의미합니다. 그것에 대해 아무것도 나쁜 있습니다. 더 이상 SQL을 사용하여 EF를 사용하여 = 여부를 거짓말이라고 글로벌 아이디어. 당신은 너무 SQL 서버 2008 R2가 :
성능이 당신을 위해 중요한 경우 당신은 더 나은 솔루션을 찾을 수 없습니다. 이 절차는 매핑과 현재 버전 중 하나를 테이블 반환 매개 변수 또는 여러 결과 집합을 지원하지 않기 때문에 EF에서 실행할 수 없습니다.
-
==============================
3.우리는 절을 포함하는 중간 테이블을 추가하고 사용하는 데 필요한 것을 LINQ 쿼리에서 해당 테이블에 가입하여 EF이 문제를 포함 해결 할 수 있었다. 우리는이 방법으로 놀라운 결과를 얻을 수 있었다. 우리는 큰 EF 모델을 가지고 "포함"우리가 사용이 절을 "포함"고 쿼리에 대한 매우 빈약 한 성과를 얻고 있었다 EF 쿼리를 미리 컴파일 할 때 사용할 수 없습니다.
우리는 절을 포함하는 중간 테이블을 추가하고 사용하는 데 필요한 것을 LINQ 쿼리에서 해당 테이블에 가입하여 EF이 문제를 포함 해결 할 수 있었다. 우리는이 방법으로 놀라운 결과를 얻을 수 있었다. 우리는 큰 EF 모델을 가지고 "포함"우리가 사용이 절을 "포함"고 쿼리에 대한 매우 빈약 한 성과를 얻고 있었다 EF 쿼리를 미리 컴파일 할 때 사용할 수 없습니다.
개요 :
-
==============================
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.내가 엔티티 프레임 워크에 익숙하지 않아요하지만 당신은 다음을 수행하는 경우 반환 한 더 나은 무엇입니까?
내가 엔티티 프레임 워크에 익숙하지 않아요하지만 당신은 다음을 수행하는 경우 반환 한 더 나은 무엇입니까?
대신이의는 :
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.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.에가 포함 된 캐시 대안?
에가 포함 된 캐시 대안?
내가 엔티티 프레임 워크 기능 제안 링크에 내 두 펜스를 추가했습니다, 그래서 이것은 단지 나에게 물었다.
SQL을 생성 할 때 문제는 확실히이다. 나는 쿼리 생성 4 초였다 데이터 누구의 클라이언트를 가지고 있지만 실행은 0.1 초였다.
나는 동적 LINQ 및 관찰 보고서를 사용할 때 SQL 세대와 마찬가지로 오래 걸려있는 것을 발견하지만 캐시 할 수있는 일이 생성됩니다. 다시 실행할 때 그래서 0.2 초 아래로했다.
는 SQL에서 여전히 생성합니다.
초기 히트를 위장 할 수 있는지 고려해야 할 다른 뭔가가, 배열의 수는 훨씬 변경하고 쿼리를 많이 실행되지 않습니다. (LINQ 패드에서 테스트)
-
==============================
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 #을 훨씬 더 빨리 달렸다.
from https://stackoverflow.com/questions/7897630/why-does-the-contains-operator-degrade-entity-frameworks-performance-so-drama by cc-by-sa and MIT license
'SQL' 카테고리의 다른 글
[SQL] SQL 서버 : 추출 테이블 메타 데이터 (설명, 필드 및 데이터 유형) (0) | 2020.04.06 |
---|---|
[SQL] 컬럼 데이터 유형에 BYTE과 CHAR 차이점 (0) | 2020.04.06 |
[SQL] 모든 텍스트 기반의 필드에 대한 일반적인 VARCHAR (255)를 사용하여이 단점은? (0) | 2020.04.06 |
[SQL] 큰 데이터베이스의 빠른에서 중복을 제거 MYSQL (0) | 2020.04.06 |
[SQL] SQL Server 2008의 선택 쿼리 결과에서 테이블을 만드는 방법 [중복] (0) | 2020.04.05 |