운영자 추정을 개선하기 위해 쿼리 변경


14

허용되는 시간 내에 실행되는 쿼리가 있지만 가능한 최대 성능을 짜고 싶습니다.

내가 개선하려는 작업은 노드 17의 계획 오른쪽에있는 "Index Seek"입니다.

여기에 이미지 설명을 입력하십시오

적절한 색인을 추가했지만 해당 작업에 대한 추정치는 계산의 절반입니다.

인덱스를 변경하고 임시 테이블을 추가하고 쿼리를 다시 작성하는 방법을 찾았지만 올바른 견적을 얻기 위해 이보다 더 단순화 할 수는 없었습니다.

누구든지 내가 시도 할 수있는 것에 대한 제안이 있습니까?

전체 계획 및 세부 사항은 여기에서 확인할 수 있습니다 .

익명이 아닌 계획은 여기에서 찾을 수 있습니다.

최신 정보:

질문의 초기 버전이 많은 혼란을 겪었다는 느낌이 들기 때문에 설명과 함께 원래 코드를 추가 할 것입니다.

create procedure [dbo].[someProcedure] @asType int, @customAttrValIds idlist readonly
as
begin
    set nocount on;

    declare @dist_ca_id int;

    select *
    into #temp
    from @customAttrValIds
        where id is not null;

    select @dist_ca_id = count(distinct CustomAttrID) 
    from CustomAttributeValues c
        inner join #temp a on c.Id = a.id;

    select a.Id
        , a.AssortmentId 
    from Assortments a
        inner join AssortmentCustomAttributeValues acav
            on a.Id = acav.Assortment_Id
        inner join CustomAttributeValues cav 
            on cav.Id = acav.CustomAttributeValue_Id
    where a.AssortmentType = @asType
        and acav.CustomAttributeValue_Id in (select id from #temp)
    group by a.AssortmentId
        , a.Id
    having count(distinct cav.CustomAttrID) = @dist_ca_id
    option(recompile);

end

답변:

  1. pasteThePlan 링크에서 초기 이름이 이상한 이유는 무엇입니까?

    답변 : SQL Sentry Plan Explorer의 익명 계획을 사용했기 때문에.

  2. OPTION RECOMPILE ?

    답변 : 매개 변수 스니핑을 피하기 위해 다시 컴파일 할 수 있기 때문에 (데이터가 왜곡 될 수 있습니다). 테스트를 마쳤으며를 사용하는 동안 Optimizer가 생성하는 계획에 만족합니다 OPTION RECOMPILE.

  3. WITH SCHEMABINDING?

    답변 : 정말로 피하고 싶고 인덱싱 된보기가있을 때만 사용하고 싶습니다. 어쨌든 이것은 시스템 기능 ( COUNT())이므로 SCHEMABINDING여기서는 사용하지 마십시오 .

더 많은 가능한 질문에 대한 답변 :

  1. 왜 사용합니까 INSERT INTO #temp FROM @customAttrributeValues 합니까?

    답변 : 쿼리에 연결된 변수를 사용할 때 변수 작업으로 인한 추정치는 항상 1임을 알았으므로 데이터를 임시 테이블에 넣는 것을 테스트 한 다음 EstimatedActual Rows 와 같습니다. .

  2. 내가 왜 사용 했습니까 and acav.CustomAttributeValue_Id in (select id from #temp) 했습니까?

    답변 : #temp에서 JOIN으로 바꿀 수 있었지만 개발자는 매우 혼란스러워 IN옵션을 제안했습니다 . 나는 대체로도 차이가있을 것이라고 생각하지 않으며 어느 쪽이든 문제가 없습니다.


나는 #temp창조와 사용이 성능의 문제가 아니라 이익이 될 것이라고 생각할 것이다. 색인화되지 않은 테이블에 저장하여 한 번만 사용합니다. 그 완전히 제거 (및 변경 시도 in (select id from #temp)exists. 하위 쿼리
ypercubeᵀᴹ

@ ypercubeᵀᴹ 사실, 임시 테이블 대신 변수를 사용하여 읽은 페이지가 몇 페이지 정도 적습니다.
Radu Gheorghiu

그건 그렇고, 테이블 변수는 Option (Recompile)과 함께 사용될 때 정확한 행 수 추정치를 제공하지만 세분화 된 통계, 카디널리티 등은 아직 없습니다.
TH

@TH 글쎄, select id from @customAttrValIds대신 실제 실행 계획에서 추정을 보았습니다. 대신 사용할 때 select id from #temp예상 행 수는 1변수 및 3#temp (실제 행 수와 일치)입니다. 그래서 내가로 교체 @했습니다 #. 그리고 나는 DO 그들이 TBL 변수를 사용 때의 추정은 항상 1이 될 것입니다 그리고 개선으로 더 좋은 평가들이 임시 테이블을 사용 얻을 수 있다고 말했다 (브렌트 O 또는 아론 버트 랜드에서) 이야기를 기억한다.
Radu Gheorghiu

@RaduGheorghiu 예. 그러나 그 사람들의 세계에서 옵션 (재 컴파일)은 거의 옵션이 아니며 다른 유효한 이유로 임시 테이블을 선호합니다. 아마도 다음과 같이 계획이 변경되기 때문에 추정치가 항상 1로 잘못 표시 될 수 있습니다. theboreddba.com/ Categories
TH

답변:


12

계획은 SQL Server 2008 R2 RTM 인스턴스 (빌드 10.50.1600)에서 컴파일되었습니다. 설치해야합니다 서비스 팩 3 (도 73), (초기) 최신 빌드 10.50.6542로 불러 최신 패치 다음 (빌드 10.50.6000)를. 이는 보안, 버그 수정 및 새로운 기능을 포함하여 여러 가지 이유로 중요합니다.

매개 변수 임베딩 최적화

현재 질문과 관련하여 SQL Server 2008 R2 RTM은 다음에 대한 PEO (Parameter Embedding Optimization)를 지원하지 않았습니다. OPTION (RECOMPILE) . 현재 주요 이점 중 하나를 실현하지 않고 재 컴파일 비용을 지불하고 있습니다.

PEO를 사용할 수 있으면 SQL Server는 쿼리 계획에서 직접 로컬 변수 및 매개 변수에 저장된 리터럴 값을 사용할 수 있습니다. 이는 극적인 단순화 및 성능 향상으로 이어질 수 있습니다. 내 기사, 매개 변수 스니핑, 포함 및 권장 옵션에 대한 자세한 내용이 있습니다. .

해시, 분류 및 교환 유출

쿼리가 SQL Server 2012 이상에서 컴파일 된 경우에만 실행 계획에 표시됩니다. 이전 버전에서는 프로파일 러 또는 확장 이벤트를 사용하여 쿼리가 실행되는 동안 유출을 모니터링해야했습니다. 유출은 항상 영구 스토리지 백업 tempdb 와 물리적 입출력을주고받습니다. 초래하며, 이는 유출이 크거나 I / O 경로에 압력이 가해지는 경우 특히 성능에 영향을 줄 수 있습니다.

실행 계획에는 두 개의 해시 일치 (Aggregate) 연산자가 있습니다. 해시 테이블 용으로 예약 된 메모리 는 출력 행예상치를 기반으로합니다 (즉, 런타임에서 찾은 그룹 수에 비례합니다). 부여 된 메모리는 실행이 시작되기 직전에 고정되며 인스턴스의 사용 가능한 메모리 양에 관계없이 실행 중에 증가 할 수 없습니다. 제공된 계획에서 두 해시 일치 (Aggregate) 연산자는 옵티마이 저가 예상 한 것보다 많은 행을 생성하므로 런타임시 tempdb 로 유출 될 수 있습니다 .

계획에 해시 일치 (내부 조인) 연산자도 있습니다. 해시 테이블 용으로 예약 된 메모리 는 프로브 측 입력 행예상치를 기반으로합니다. . 프로브 입력은 847,399 행을 추정하지만 런타임시 1,223,636이 발생합니다. 이 초과로 인해 해시 유출이 발생할 수도 있습니다.

중복 집계

노드 8의 해시 일치 (집계)는에 대해 그룹화 작업을 수행 (Assortment_Id, CustomAttrID)하지만 입력 행은 출력 행과 같습니다.

노드 8 해시 일치 (집계)

이것은 열 조합이 핵심임을 나타냅니다 (따라서 그룹화는 의미 상 불필요합니다). 중복 집계 수행 비용은 해시 파티셔닝 교환 (양쪽의 병렬 처리 연산자)에서 140 만 행을 두 번 전달해야하므로 증가합니다.

관련된 열이 다른 테이블에서 온 것으로 가정하면,이 고유 정보를 옵티 마이저에 전달하는 것이 평소보다 어렵 기 때문에 중복 그룹화 조작 및 불필요한 교환을 피할 수 있습니다.

비효율적 인 스레드 분배

Joe Obbish의 답변 에서 언급했듯이 노드 14에서의 교환은 해시 분할을 사용하여 행을 스레드간에 분배합니다. 불행히도 적은 수의 행과 사용 가능한 스케줄러는 세 개의 행이 모두 단일 스레드로 끝나는 것을 의미합니다. 명백히 평행 한 계획은 노드 9에서의 교환에 이르기까지 직렬로 (병렬 오버 헤드로) 실행됩니다.

노드 13에서 구별 정렬을 제거하여 라운드 로빈 또는 브로드 캐스트 파티셔닝을 얻기 위해이를 해결할 수 있습니다. 가장 쉬운 방법은 #temp테이블 에 클러스터 된 기본 키를 작성하고 테이블을로드 할 때 고유 한 조작을 수행하는 것입니다.

CREATE TABLE #Temp
(
    id integer NOT NULL PRIMARY KEY CLUSTERED
);

INSERT #Temp
(
    id
)
SELECT DISTINCT
    CAV.id
FROM @customAttrValIds AS CAV
WHERE
    CAV.id IS NOT NULL;

임시 테이블 통계 캐싱

를 사용하더라도 OPTION (RECOMPILE)SQL Server는 여전히 프로 시저 호출간에 임시 테이블 개체 와 관련 통계를 캐시 할 수 있습니다 . 이는 일반적으로 성능을 최적화하는 것이지만 임시 테이블에 인접 프로 시저 호출에서 비슷한 양의 데이터가 채워지면 재 컴파일 된 계획은 잘못된 통계 (이전 실행에서 캐시 됨)를 기반으로 할 수 있습니다. 자세한 내용은 내 기사, 저장 프로 시저의 임시 테이블설명 된 임시 테이블 캐싱에 자세히 설명되어 있습니다.

이를 피하려면 임시 테이블이 채워진 후 그리고 쿼리에서 참조되기 전에 OPTION (RECOMPILE)명시 적과 함께 사용 UPDATE STATISTICS #TempTable하십시오.

쿼리 재 작성

이 부분에서는 #Temp테이블 작성에 대한 변경 이 이미 수행되었다고 가정합니다 .

가능한 해시 유출 및 중복 집계 (및 주변 교환)의 비용을 감안할 때 노드 10에서 세트를 구체화하는 데 비용을 지불 할 수 있습니다.

CREATE TABLE #Temp2
(
    CustomAttrID integer NOT NULL,
    Assortment_Id integer NOT NULL,
);

INSERT #Temp2
(
    Assortment_Id,
    CustomAttrID
)
SELECT
    ACAV.Assortment_Id,
    CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
    ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
    ON T.id = ACAV.CustomAttributeValue_Id;

ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);

PRIMARY KEY인덱스 빌드를 정확한 카디널리티 정보가 확인하고, 문제를 캐싱 임시 테이블 통계를 방지하기 위해 별도의 단계에 추가됩니다.

이 구체화는 인스턴스에 사용 가능한 메모리가 충분한 경우 메모리에서 발생할 가능성이 높습니다 ( tempdb I / O 방지 ). SQL Server 2012 (SP1 CU10 / SP2 CU1 이상)로 업그레이드하면 Eager Write 동작향상 되어 훨씬 더 가능성이 높습니다 .

이 조치는 옵티 마이저에 중간 세트에 대한 정확한 카디널리티 정보를 제공하고 통계를 작성 (Assortment_Id, CustomAttrID)하며 키로 선언 할 수있게합니다 .

모집단 계획 #Temp2은 다음과 같아야합니다 (클러스터형 인덱스 스캔 #Temp, 구별 정렬 없음, 교환은 이제 라운드 로빈 행 분할 사용).

# Temp2 인구

해당 세트를 사용하면 최종 쿼리는 다음과 같습니다.

SELECT
    A.Id,
    A.AssortmentId
FROM
(
    SELECT
        T.Assortment_Id
    FROM #Temp2 AS T
    GROUP BY
        T.Assortment_Id
    HAVING
        COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
    ON A.Id = DT.Assortment_Id
WHERE
    A.AssortmentType = @asType
OPTION (RECOMPILE);

우리 COUNT_BIG(DISTINCT...는 간단하게 수동으로 다시 작성할 수 COUNT_BIG(*)있지만 새로운 주요 정보를 사용하면 최적화 프로그램이 다음을 수행합니다.

최종 계획

최종 계획은 내가 액세스 할 수없는 데이터에 대한 통계 정보에 따라 루프 / 해시 / 병합 조인을 사용할 수 있습니다. 다른 작은 참고 사항 : 나는 같은 색인이 있다고 가정했습니다 CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);.

어쨌든 최종 계획의 중요한 점은 추정치가 훨씬 우수해야하며 복잡한 그룹화 작업 순서가 단일 스트림 집계 (메모리가 필요하지 않으므로 디스크로 유출 할 수 없음)로 줄었다는 것입니다.

이 경우 여분의 임시 테이블을 사용 하면 실제로 성능이 향상 될 것이라고 말하기는 어렵지만 , 추정치 및 계획 선택은 시간이 지남에 따라 데이터 볼륨 및 배포 변경에 대해 훨씬 더 탄력적입니다. 오늘날 약간의 성능 향상보다 장기적으로 더 가치가있을 수 있습니다. 어쨌든 이제 최종 결정의 근거가되는 훨씬 더 많은 정보가 있습니다.


9

쿼리의 카디널리티 추정치는 실제로 매우 좋습니다. 실제 행 수와 정확하게 일치하는 예상 행 수를 얻는 것은 드문 일입니다. 특히이 수가 많은 조인이있는 경우에는 더욱 그렇습니다. 최적화 카디널리티 추정은 옵티마이 저가 제대로 이해하기 어렵습니다. 주의해야 할 중요한 사항은 중첩 루프의 내부 부분에 대한 예상 행 수가 해당 루프의 실행 당 수라는 것입니다. 따라서 SQL Server가 인덱스 검색으로 463869 행을 가져올 것이라고 말한 경우이 경우 실제 추정치는 실행 횟수 (2) * 463869 = 927738이며 실제 행 수 (1391608)와 크게 다르지 않습니다. 노드 ID 10에서 중첩 루프 조인 직후에 예상 행 수는 거의 완벽합니다.

잘못된 카디널리티 추정은 쿼리 최적화 프로그램이 잘못된 계획을 선택하거나 계획에 충분한 메모리를 부여하지 않을 때 주로 문제가됩니다. 이 계획에 대한 tempdb 유출이 보이지 않으므로 메모리는 괜찮아 보입니다. 호출 한 중첩 루프 조인의 경우 작은 외부 테이블과 인덱스 된 내부 테이블이 있습니다. 무슨 일이야? 정확히 말하면, 쿼리 최적화 프로그램이 여기서 다르게 수행 할 것으로 예상하는 것은 무엇입니까?

성능 향상 측면에서 볼 때 SQL Server는 해시 알고리즘을 사용하여 병렬 행을 분산시켜 모든 행이 동일한 스레드에 있음을 나타냅니다.

나사산 불균형

결과적으로, 하나의 스레드가 인덱스 탐색에 대한 모든 작업을 수행합니다.

스레드 불균형 찾기

즉, 파티션 ID가 노드 ID 9에서 연산자를 다시 스트리밍 할 때까지 쿼리가 효과적으로 병렬로 실행되지 않습니다. 각 행이 자체 스레드에서 끝나도록 라운드 로빈 분할이 필요합니다. 그러면 두 스레드가 노드 ID 17에 대한 인덱스 검색을 수행 TOP할 수 있습니다 . 불필요한 연산자를 추가 하면 라운드 로빈 분할이 발생할 수 있습니다. 원하는 경우 여기에 세부 사항을 추가 할 수 있습니다.

카디널리티 예상치에 초점을 맞추려면 첫 번째 조인 후 행을 임시 테이블에 넣을 수 있습니다. 임시 테이블에서 통계를 수집하면 최적화 프로그램이 호출 한 중첩 루프 조인의 외부 테이블에 대한 추가 정보를 제공합니다. 라운드 로빈 분할이 발생할 수도 있습니다.

추적 플래그 4199 또는 2301을 사용하지 않는 경우이를 고려할 수 있습니다. 추적 플래그 4199 는 다양한 옵티 마이저 수정 사항을 제공하지만 일부 워크로드를 저하시킬 수 있습니다. 추적 플래그 2301 은 쿼리 최적화 프로그램의 조인 카디널리티 가정 중 일부를 변경하여 작동을 어렵게 만듭니다. 두 경우 모두 사용하기 전에 신중하게 테스트하십시오.


-2

1.4mill가 테이블의 충분한 부분이 아니 어서 옵티마이 저가 해시 또는 병합 조인으로 인덱스 (클러스터 아님) 스캔을 선택하지 않는 한 조인에 대한 더 나은 견적을 얻는 것이 계획을 변경하지 않을 것이라고 생각합니다. 나는 이것이 사실이 아니며 실제로 도움이되지 않을 것이라고 생각하지만 CustomAttributeValues 대한 내부 조인내부 해시 조인내부 병합 조인 으로 대체하여 효과를 테스트 할 수 있습니다 .

또한 코드를 더 광범위하게 살펴 보았으며 코드를 개선 할 방법을 찾지 못했습니다. 물론 잘못된 것으로 입증되고 싶습니다. 그리고 당신이하려는 일의 전체 논리를 게시하고 싶다면 다른 모습에 관심이 있습니다.


3
조인 순서 및 중첩, 병렬 처리, 로컬 / 글로벌 집계 등을위한 옵션이 많은 해당 쿼리에 대한 계획 공간이 매우 넓습니다.이 옵션의 대부분은 파생 통계 (분산 및 원시 카디널리티)의 변경에 영향을받습니다. 또한 조인 힌트는 일반적으로 silent와 함께 제공 OPTION(FORCE ORDER)되므로 옵티마이 저가 텍스트 순서에서 조인 순서를 바꾸지 못하고 기타 여러 가지 최적화가 수행되지 않기 때문에 피해야 합니다.
Paul White 9

-12

[클러스터되지 않은] 인덱스 검색에서 개선하지 않을 것입니다. 비 클러스터형 인덱스 탐색보다 나은 유일한 방법은 클러스터형 인덱스 탐색입니다.

또한 지난 10 년 동안 SQL DBA를, 그 전에 5 년 동안 SQL 개발자로 근무한 경험이 있으며, 실제로는 수행 할 수있는 실행 계획을 연구하여 SQL 쿼리를 개선하는 것이 매우 드 rare니다. 다른 방법으로 찾지 마십시오. 실행 계획을 생성하는 주된 이유는 성능 향상을 위해 추가 할 수있는 누락 된 인덱스를 제안하기 때문입니다.

비효율적 인 경우 SQL 쿼리 자체를 조정하면 성능이 크게 향상됩니다. 예를 들어, 몇 달 전에 SELECT UNION SELECT표준 SQL PIVOT연산자 를 사용하도록 스타일 피벗 테이블을 다시 작성하여 160 배 더 빠르게 실행되는 SQL 함수를 얻었습니다 .

insert into Variable1 values (?), (?), (?)


select *
    into Object1
    from Variable2
        where Column1 is not null;



select Variable3 = Function1(distinct Column2) 
    from Object2 Object3
        inner join Object1 Object4 on Object3.Column1 = Object4.Column1;



select Object4.Column1
        , Object4.Column3 
    from Object5 Object4
        inner join Object6 Object7
            on Object4.Column1 = Object7.Column4
        inner join Object2 Object8 
            on Object8.Column1 = Object7.Column5
    where Object4.Column6 = Variable4
        and Object7.Column5 in (select Column1 from Object1)
    group by Object4.Column3
        , Object4.Column1
    having Function1(distinct Object8.Column2) = Variable3
    option(recompile);

보시 SELECT * INTO다시피 일반적으로 표준보다 효율이 떨어집니다 INSERT Object1 (column list) SELECT column list. 다시 작성하겠습니다. 다음으로 Function1이 a없이 정의 된 경우 절을 WITH SCHEMABINDING추가하면 WITH SCHEMABINDING더 빨리 실행될 수 있습니다.

Object2를 Object3로 별명 지정하는 것과 같이 의미가없는 별명을 많이 선택했습니다. 코드를 난독 처리하지 않는 더 나은 별칭을 선택해야합니다. "Object7.Column5 in (Object1에서 Column1 선택)"이 있습니다.

IN이 특성의 절은 항상보다 효율적으로 작성됩니다 EXISTS (SELECT 1 FROM Object1 o1 WHERE o1.Column1 = Object7.Column5). 아마도 나는 다른 방법으로 그것을 써야했을 것입니다. EXISTS항상 최소한만큼 좋습니다 IN. 항상 낫지는 않지만 일반적으로 좋습니다.

또한 option(recompile)여기에서 쿼리 성능이 향상되고 있는지 의심 됩니다. 나는 그것을 제거 테스트합니다.


6
비 클러스터형 인덱스 찾기가 쿼리를 다루는 경우, 클러스터 인덱스에는 모든 열이 있고 클러스터되지 않은 인덱스는 더 적은 수의 페이지 탐색을 요구하기 때문에 클러스터 인덱스 검색보다 거의 항상 낫습니다. b- 트리로 들어가는 단계 수를 줄이면 데이터를 검색 할 수 있습니다. 따라서 클러스터형 인덱스 탐색이 항상 더 좋다고 말하는 것은 정확하지 않습니다 .
ErikE
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.