programing

포함()을 여러 번 사용할 때 엔티티 프레임워크 코드가 느림

oldcodes 2023. 6. 23. 22:24
반응형

포함()을 여러 번 사용할 때 엔티티 프레임워크 코드가 느림

느린 코드를 디버깅해보니 아래에 게시된 EF 코드가 원인인 것 같습니다.이후 단계에서 쿼리를 평가하는 데 4-5초가 걸립니다.1초 안에 실행되도록 하는 중입니다.

SQL Server Profiler를 사용하여 테스트해 본 결과 SQL 스크립트가 여러 개 실행되는 것 같습니다.또한 SQL 서버 실행이 완료되기까지 3-4초가 소요됩니다.

Include()의 사용에 대한 다른 유사한 질문을 읽었는데 사용 시 성능 저하가 있는 것 같습니다.아래 코드를 여러 개의 다른 쿼리로 분할하려고 했지만 큰 차이가 없습니다.

어떻게 하면 아래 항목을 더 빨리 실행할 수 있는지 아십니까?

현재 제가 작업 중인 웹 앱은 아래의 완료를 기다리는 동안 빈 iframe을 보여주고 있습니다.실행 시간을 단축할 수 없는 경우 분할하여 iframe에 데이터를 부분적으로 로드하거나 다른 비동기식 솔루션을 사용해야 합니다.여기에 있는 어떤 아이디어도 감사할 것입니다!

using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
        {
            formInstance = context.FormInstanceSet
                                .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormSectionDefinitions).Include(fs => fs.FormStateDefinitionEditableSections))
                                .Includes(x => x.Include(fi => fi.FormDefinition).Include(fd => fd.FormStateDefinitions))
                                .Includes(x => x.Include(fi => fi.FormSectionInstances).Include(fs => fs.FormFieldInstances).Include(ff => ff.FormFieldDefinition).Include(ffd => ffd.FormFieldMetaDataDefinition).Include(ffmdd => ffmdd.ComplexTypePropertyNames))
                                .Include(x => x.CurrentFormStateInstance)      
                                .Include(x => x.Files)
                                .FirstOrDefault(x => x.FormInstanceIdentifier == formInstanceIdentifier);

            scope.Complete();
        }

tl;dr 다중IncludeSQL 결과 집합을 블로우업합니다.곧 하나의 메가 문을 실행하는 대신 여러 데이터베이스 호출을 통해 데이터를 로드하는 것이 저렴해집니다.최적의 혼합물을 찾으십시오.Include그리고.Load진술들.

포함을 사용할 때 성능 저하가 있는 것 같습니다.

그런 말로는 부족해요!IncludeSQL 쿼리 결과를 가로와 세로 모두 빠르게 확대할 수 있습니다.왜 그런 것일까요?

Includes

(이 파트는 Entity Framework classic, v6 및 이전 버전에 적용됨)

예를 들어, 우리가.

  • 엔티티 루트티Root
  • 엔티티 체Root.Parent
  • 엔티티 동실Root.Children1그리고.Root.Children2
  • 문 LINQ 성명Root.Include("Parent").Include("Children1").Include("Children2")

그러면 다음 구조를 가진 SQL 문이 구축됩니다.

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children1

UNION

SELECT *, <PseudoColumns>
FROM Root
JOIN Parent
JOIN Children2

것들이.<PseudoColumns>와 같은 표현으로 구성되어 있습니다.CAST(NULL AS int) AS [C2],그리고 그들은 모두 같은 양의 열을 갖는 역할을 합니다.UNION-ed 쿼리.은 첫번부다대유한추열다가니합을사음에에 합니다.Child2▁for▁pseudo▁the▁adds에 의사 열이 추가됩니다.Child1.

SQL 결과 집합의 크기는 다음과 같습니다.

  • 의 열 수SELECT은 네 의 표에 입니다.
  • 는 포함된 하위 컬렉션의 레코드 합계입니다.

총 데이터 포인트 수가 다음과 같기 때문입니다.columns * rows의 추가적인 것.Include결과 집합의 총 데이터 점 수가 기하급수적으로 증가합니다.제가 그것을 증명해 보겠습니다.Root 이제 인 시다, 이추가로와 .Children3행이 을 얻을 수 있습니다.모든 테이블에 5개의 열과 100개의 행이 있는 경우 다음을 얻을 수 있습니다.

나.Include(Root1개의 자식 컬렉션): 10개의 열 * 100개의 행 = 1000개의 데이터 점.
Includes(s)Root2개의 자식 컬렉션): 15개의 열 * 200개의 행 = 3000개의 데이터 점.
Includes(s)Root3개의 자식 컬렉션): 20개의 열 * 300개의 행 = 6000개의 데이터 포인트.

12개 포함Includes이는 78,000개의 데이터 포인트에 해당합니다!

가 아닌 모든 에는 12개의 레코드가 필요합니다.Includes,당신은 가지고 있다13 * 5 * 100데이터 포인트: 6500, 10% 미만!

이 숫자들은 데이터 포인트의 대부분이 다음과 같을 것이라는 점에서 다소 과장되어 있습니다.null따라서 클라이언트로 전송되는 결과 집합의 실제 크기에 크게 영향을 주지 않습니다. query optimizer의 에 따라 인 영향을 .Includes의

균형.

그래서 사용하기Includes데이터베이스 호출 비용과 데이터 볼륨 간의 미묘한 균형입니다., 이상일 을 빠르게 할 수 .Includes컬렉션을 것입니다. ( 부모님을 위한 꽤더.)Includes결과 집합을 넓힐 뿐입니다.

대안

Include별도의 쿼리에 데이터를 로드하는 것입니다.

context.Configuration.LazyLoadingEnabled = false;
var rootId = 1;
context.Children1.Where(c => c.RootId == rootId).Load();
context.Children2.Where(c => c.RootId == rootId).Load();
return context.Roots.Find(rootId);

이렇게 하면 필요한 모든 데이터가 컨텍스트의 캐시에 로드됩니다.이 프로세스 동안 EF는 탐색 속성을 자동으로 채우는 관계 픽스업을 실행합니다.Root.Children등)을 로드된 엔티티에 표시됩니다.최종 결과는 다음을 포함하는 문장과 동일합니다.Include 단 한 .s, 단한가중한는차요이제고는외하하컬렉션위티상엔로티이것점로표트레합관않서해드경된액로컬세우스할이거하지려리고다드니를에션렉당므으지되시으로에자리지태을는▁s▁collect▁the:rence▁child▁manager▁diffe▁importantions,▁except▁loading▁are▁if▁try로레▁for▁state트합▁lazy하려▁one거경고그렇기 때문에 게으른 부하를 끄는 것이 중요합니다.

은 실로제, 당은어조의 조합인지 할 입니다.Include그리고.Load진술이 가장 적합합니다.

고려해야 할 기타 측면

Include또한 쿼리 복잡성을 증가시키므로 데이터베이스의 쿼리 최적화 도구는 최상의 쿼리 계획을 찾기 위해 점점 더 많은 노력을 기울여야 합니다.어느 시점에서 이것은 더 이상 성공하지 못할 수도 있습니다. 외부 키의 또일중예인누경우락된가덱다다스키있니수습저한음성하될이면능추을가하부요외부예▁▁are▁addingance다▁may▁suffer▁by)있니습▁perform▁indexes또▁keys▁when▁vital수▁foreign▁some)를 추가하면 성능이 저하될 수 있습니다.Include계획을 하더라도 최의질 불구도하 고상계에 획의▁s 고하불.

엔티티 프레임워크 코어

데카르트 폭발

어떤 이유로 인해 위에 설명된 동작인 UNIXED 쿼리가 EF core 3에서 포기되었습니다.이제 조인을 사용하여 하나의 쿼리를 작성합니다.쿼리가 "별1" 모양이면 SQL 결과 집합에서 데카르트 폭발이 발생합니다.는 이 깨진 변화를 알리는 메모만 찾을 수 있을 뿐, 왜 그런지는 적혀 있지 않습니다.

쿼리 분할

이러한 데카르트식 폭발에 대응하기 위해 엔티티 프레임워크 코어 5는 여러 쿼리에 관련 데이터를 로드할 수 있는 분할 쿼리 개념을 도입했습니다.따라서 하나의 대규모 다중 SQL 결과 세트를 구축할 수 없습니다.또한 쿼리 복잡성이 낮으므로 여러 번 왕복하더라도 데이터를 가져오는 데 걸리는 시간을 줄일 수 있습니다.그러나 동시 업데이트가 발생하면 데이터가 일관되지 않을 수 있습니다.


1쿼리 루트에서 벗어난 여러 개의 1:n 관계입니다.

닷넷 코어 5를 사용하여 이 솔루션을 사용했습니다.

_context.ChangeTracker.LazyLoadingEnabled = false;
_context.ChangeTracker.AutoDetectChangesEnabled = false;
var mainObj = _context.MarinzonServiceItems.Where(filter);
var returnQuery = mainObj.Include(x => x.Service);
returnQuery.Include(x => x.User).Load();
returnQuery.Include(x => x.Category).Load();
returnQuery.Include(x => x.FAQQuestions).Load();
returnQuery.Include(x => x.FAQServices).Load();
returnQuery.Include(x => x.ServiceItemServices.Where(x => x.IsActive == true)).ThenInclude(x => x.ServiceItemServicePrices).Load();
return returnQuery;

15개 이상의 "포함" 문이 있고 7분 만에 2M 이상의 행 결과를 생성한 쿼리와 유사한 문제가 있습니다.

저에게 효과적인 솔루션은 다음과 같습니다.

  1. 레이지 로드 사용 안 함
  2. 자동 탐지 변경 사용 안 함
  3. 큰 쿼리를 작은 청크로 분할

샘플은 아래에서 확인할 수:

public IQueryable<CustomObject> PerformQuery(int id) 
{
 ctx.Configuration.LazyLoadingEnabled = false;
 ctx.Configuration.AutoDetectChangesEnabled = false;

 IQueryable<CustomObject> customObjectQueryable = ctx.CustomObjects.Where(x => x.Id == id);

 var selectQuery = customObjectQueryable.Select(x => x.YourObject)
                                                  .Include(c => c.YourFirstCollection)
                                                  .Include(c => c.YourFirstCollection.OtherCollection)
                                                  .Include(c => c.YourSecondCollection);

 var otherObjects = customObjectQueryable.SelectMany(x => x.OtherObjects);

 selectQuery.FirstOrDefault();
 otherObjects.ToList();

 return customObjectQueryable;
 }

서버 측에서 모든 필터링을 수행하려면 IQueryable이 필요합니다.IEnumberable은 메모리에서 필터링을 수행할 수 있으며 이는 매우 많은 시간이 소요되는 프로세스입니다.Entity Framework가 메모리의 모든 연결을 복구합니다.

'포함'하려는 모든 엔티티 간의 관계를 올바르게 구성했습니까?하나 이상의 엔티티가 다른 엔티티와 관계가 없는 경우 EF는 SQL 조인 구문을 사용하여 하나의 복잡한 쿼리를 구성할 수 없습니다. 대신 사용자가 가진 '포함'만큼의 쿼리를 실행합니다.물론 성능 문제가 발생할 수 있습니다.데이터를 얻기 위해 EF가 생성하는 정확한 쿼리(-es)를 게시해 주시겠습니까?

  1. 쿼리를 시작할 때 해당 분산 트랜잭션을 제거합니다.이는 말 그대로 단순 트랜잭션을 분산 트랜잭션으로 승격시킴으로써 애플리케이션을 죽이는 것입니다.
  2. ReadUncommitted 트랜잭션 범위를 사용하면 응용 프로그램이 반복적인 읽기에 취약해집니다.
  3. 이러한 엔티티를 업데이트하지 않을 경우 AsNoTracking을 사용해야 합니다.추적은 프록시를 생성하고 프록시를 생성하기 위한 EF 로직은 성능을 크게 저하시킵니다.
  4. Server인 SQL SQL Server에서 SQL Profiler와 함께 합니다.SET STATISTICS TIME ON; SET STATISTICS IO ON;창.com 에 . statisticsparser.com 에 붙여넣습니다.

언급URL : https://stackoverflow.com/questions/34724196/entity-framework-code-is-slow-when-using-include-many-times

반응형