실행 계획을 사용하여 T-SQL 쿼리를 최적화하는 방법


15

시행 착오 및 실행 계획을 사용하여 최적화하려고 지난 이틀 동안 보낸 SQL 쿼리가 있지만 아무 소용이 없습니다. 이 일을 용서해주십시오. 그러나 전체 실행 계획을 여기에 게시하겠습니다. 쿼리 및 실행 계획에서 테이블 및 열 이름을 간략하게 만들고 회사의 IP를 보호하기 위해 노력했습니다. 실행 계획은 SQL Sentry Plan Explorer 로 열 수 있습니다 .

나는 상당한 양의 T-SQL을 수행했지만 쿼리를 최적화하기 위해 실행 계획을 사용하는 것이 새로운 영역이며 실제로 수행 방법을 이해하려고 노력했습니다. 따라서 누군가 나를 도와 주고이 실행 계획을 해독하여 쿼리에서 최적화하는 방법을 찾는 방법을 설명 할 수 있다면 영원히 감사 할 것입니다. 최적화 할 쿼리가 더 많이 있습니다. 첫 번째 질문에 도움이되는 스프링 보드 만 있으면됩니다.

이것은 쿼리입니다.

DECLARE @Param0 DATETIME     = '2013-07-29';
DECLARE @Param1 INT          = CONVERT(INT, CONVERT(VARCHAR, @Param0, 112))
DECLARE @Param2 VARCHAR(50)  = 'ABC';
DECLARE @Param3 VARCHAR(100) = 'DEF';
DECLARE @Param4 VARCHAR(50)  = 'XYZ';
DECLARE @Param5 VARCHAR(100) = NULL;
DECLARE @Param6 VARCHAR(50)  = 'Text3';

SET NOCOUNT ON

DECLARE @MyTableVar TABLE
(
    B_Var1_PK int,
    Job_Var1 varchar(512),
    Job_Var2 varchar(50)
)

INSERT INTO @MyTableVar (B_Var1_PK, Job_Var1, Job_Var2) 
SELECT B_Var1_PK, Job_Var1, Job_Var2 FROM [fn_GetJobs] (@Param1, @Param2, @Param3, @Param4, @Param6);

CREATE TABLE #TempTable
(
    TTVar1_PK INT PRIMARY KEY,
    TTVar2_LK VARCHAR(100),
    TTVar3_LK VARCHAR(50),
    TTVar4_LK INT,
    TTVar5 VARCHAR(20)
);

INSERT INTO #TempTable
SELECT DISTINCT
    T.T1_PK,
    T.T1_Var1_LK,
    T.T1_Var2_LK,
    MAX(T.T1_Var3_LK),
    T.T1_Var4_LK
FROM
    MyTable1 T
    INNER JOIN feeds.MyTable2 A ON A.T2_Var1 = T.T1_Var4_LK
    INNER JOIN @MyTableVar B ON B.Job_Var2 = A.T2_Var2 AND B.Job_Var1 = A.T2_Var3
GROUP BY T.T1_PK, T.T1_Var1_LK, T.T1_Var2_LK, T.T1_Var4_LK

-- This is the slow statement...
SELECT 
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK,
    C.C_Var1_PK,
    SUM(CONVERT(DECIMAL(18,4), A.A_Var1) + CONVERT(DECIMAL(18,4), A.A_Var2))
FROM #TempTable T
    INNER JOIN TableA (NOLOCK) A ON A.A_Var4_FK_LK  = T.TTVar1_PK
    INNER JOIN @MyTableVar     B ON B.B_Var1_PK     = A.Job
    INNER JOIN TableC (NOLOCK) C ON C.C_Var2_PK     = A.A_Var5_FK_LK
    INNER JOIN TableD (NOLOCK) D ON D.D_Var1_PK     = A.A_Var6_FK_LK
    INNER JOIN TableE (NOLOCK) E ON E.E_Var1_PK     = A.A_Var7_FK_LK  
    LEFT OUTER JOIN feeds.TableF (NOLOCK) F ON F.F_Var1 = T.TTVar5
WHERE A.A_Var8_FK_LK = @Param1
GROUP BY
    CASE E.E_Var1_LK
        WHEN 'Text1' THEN T.TTVar2_LK + '_' + F.F_Var1
        WHEN 'Text2' THEN T.TTVar2_LK + '_' + F.F_Var2
        WHEN 'Text3' THEN T.TTVar2_LK
    END,
    T.TTVar4_LK,
    T.TTVar3_LK,
    CASE E.E_Var1_LK 
        WHEN 'Text1' THEN F.F_Var1
        WHEN 'Text2' THEN F.F_Var2
        WHEN 'Text3' THEN T.TTVar5
    END,
    A.A_Var3_FK_LK, 
    C.C_Var1_PK


IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END
IF OBJECT_ID(N'tempdb..#TempTable') IS NOT NULL
BEGIN
    DROP TABLE #TempTable
END

내가 찾은 것은 세 번째 진술 (느린 것으로 언급 된)이 가장 많은 시간을 소비하는 부분이라는 것입니다. 이 두 문장은 거의 즉시 돌아온다.

실행 계획은 이 링크 에서 XML로 제공됩니다 .

마우스 오른쪽 버튼을 클릭하고 저장 한 다음 브라우저에서 열지 않고 SQL Sentry Plan Explorer 또는 다른보기 소프트웨어에서 여는 것이 좋습니다.

테이블이나 데이터에 대한 정보가 더 필요하면 언제든지 문의하십시오.


2
귀하의 통계는 벗어났습니다. 마지막으로 인덱스 조각 모음을 수행하거나 통계를 업데이트 한 시간은 언제입니까? 또한 옵티마이 저가 실제로 테이블 변수에 통계를 사용할 수 없으므로 테이블 변수 @MyTableVar 대신 임시 테이블을 사용하려고합니다.
Adam Haines

답장을 보내 주셔서 감사합니다 아담. @MyTableVar를 임시 테이블로 변경해도 아무런 영향을 미치지 않지만 행 수는 적습니다 (실행 계획에서 볼 수 있음). 실행 계획에서 내 통계가 벗어났다는 것은 무엇입니까? 재구성 또는 재구성 할 인덱스와 통계를 업데이트해야하는 테이블을 표시합니까?
Neo

3
오른쪽 하단의 해시 조인에는 빌드 입력에 약 24,000 개의 행이 있지만 실제 3,285,620 개가 있으므로 유출 될 수 있습니다 tempdb. (가) 간의 조인의 결과 행에 대한 평가, 즉 TableA@MyTableVar해제 방법을됩니다. 또한 정렬에 들어가는 행 수는 예상보다 훨씬 많아서 유출 될 수 있습니다.
Martin Smith

답변:


22

주요 답변을 얻기 전에 업데이트해야 할 두 가지 소프트웨어가 있습니다.

필요한 소프트웨어 업데이트

첫 번째는 SQL Server입니다. SQL Server 2008 서비스 팩 1 (빌드 2531)을 실행 중입니다. 최소한 현재 서비스 팩 (SQL Server 2008 서비스 팩 3-빌드 5500)으로 패치해야합니다. 작성 시점의 최신 SQL Server 2008 빌드는 서비스 팩 3, 누적 업데이트 12 (빌드 5844)입니다.

두 번째 소프트웨어는 SQL Sentry Plan Explorer입니다. 입니다. 최신 버전에는 전문가 분석을 위해 쿼리 계획을 직접 업로드하는 기능 (어디서나 XML을 붙여 넣을 필요가 없음)을 포함하여 중요한 새로운 기능과 수정 사항이 있습니다.

쿼리 계획 분석

명령문 레벨 재 컴파일 덕분에 테이블 변수에 대한 카디널리티 추정이 정확히 맞습니다.

테이블 변수 추정

불행히도 테이블 변수는 분산 통계를 유지하지 않으므로 모든 최적화 프로그램은 6 개의 행이 있다는 것을 알고 있습니다. 이 6 개의 행에있을 수있는 값을 전혀 모릅니다. 이 정보는 다음 작업이 다른 테이블에 대한 조인이라는 점에서 중요합니다. 해당 조인의 카디널리티 추정치는 옵티마이 저의 거친 추측을 기반으로합니다.

첫 조인 견적

이때부터 옵티마이 저가 선택한 계획은 잘못된 정보를 기반으로하므로 성능이 너무 나쁘다는 것은 놀라운 일이 아닙니다. 특히 정렬과 해시 조인에 대한 해시 테이블을 제외하고 메모리 세트는 너무 작습니다. 실행시 오버플로 정렬 및 해싱 작업이 물리적 으로 유출됩니다. tempdb 디스크 .

SQL Server 2008은이를 실행 계획에서 강조하지 않습니다. 확장 이벤트 또는 프로파일 러 정렬 경고해시 경고를 사용하여 유출을 모니터링 할 수 있습니다 . 메모리는 실행이 시작되기 전의 카디널리티 추정을 기반으로 정렬 및 해시를 위해 예약되어 있으며 SQL Server의 예비 메모리 용량에 관계없이 실행 중에 증가시킬 수 없습니다. 따라서 정확한 행 수 추정은 작업 공간 메모리 소비 작업과 관련된 모든 실행 계획에 중요합니다.

쿼리도 매개 변수화됩니다. OPTION (RECOMPILE)다른 매개 변수 값이 쿼리 계획에 영향을주는 경우 쿼리에 추가 하는 것을 고려해야 합니다. 어쨌든 그것을 사용하는 것을 고려해야하므로 최적화 프로그램은@Param1 컴파일 타임 . 다른 것이 없다면, 이는 테이블이 매우 크고 분할되어 있기 때문에 옵티마이 저가 위에 표시된 인덱스 탐색에 대해보다 합리적인 추정치를 생성하는 데 도움이 될 수 있습니다. 정적 파티션 제거를 활성화 할 수도 있습니다.

테이블 변수 대신 임시 테이블을 사용하여 쿼리를 다시 시도하십시오 OPTION (RECOMPILE). 또한 다른 임시 테이블에 대한 첫 번째 조인 결과를 구체화하고 나머지 쿼리를 이에 대해 실행해야합니다. 행의 수가 크지 않은 것은 아니므로 (3,285,620) 이것은 상당히 빠릅니다. 그런 다음 옵티마이 저는 조인 결과에 대한 정확한 카디널리티 추정 및 분배 통계를 갖습니다. 운이 좋으면 계획의 나머지 부분이 훌륭하게 적용됩니다.

계획에 표시된 특성에서 작업하는 구체화 쿼리는 다음과 같습니다.

SELECT
    A.A_Var7_FK_LK,
    A.A_Var4_FK_LK,
    A.A_Var6_FK_LK, 
    A.A_Var5_FK_LK,
    A.A_Var1,
    A.A_Var2,
    A.A_Var3_FK_LK
INTO #AnotherTempTable
FROM @MyTableVar AS B
JOIN TableA AS A
    ON A.Job = B.B_Var1_PK
WHERE
    A_Var8_FK_LK = @Param1;

당신은 또한 수있는 INSERT미리 정의 된 임시 테이블에 (내가 그 부분을 할 수 있도록 올바른 데이터 유형은, 계획에 표시되지 않습니다). 새로운 임시 테이블은 클러스터 및 비 클러스터형 인덱스의 이점을 얻거나 얻지 못할 수 있습니다.


이 심층 답변에 정말 감사합니다. 답장을받는 데 1 주일이 걸렸습니다. 매일 다른 작업과 함께이 작업을 해왔습니다. TableA와의 조인을 구체화하는 제안을 구현했습니다 #AnotherTempTable. 이것은 @MyTableVar의 테이블 변수 대신 임시 테이블을 사용하고 OPTION (RECOMPILE)전혀 영향을 미치지 않거나 전혀 영향을 미치지 않는 다른 제안 인 가장 큰 영향을 미치는 것으로 보였습니다 .'Anonymize '및'Post to SQLPerformance.com ' SQL Sentry Plan Explorer의 옵션은 훌륭합니다. 방금 사용했습니다. answers.sqlperformance.com/questions/1087
Neo

-6

@MyTableVar에 PK가 있어야하며 #MyTableVar가 더 나은 성능을 발휘한다는 데 동의합니다 (특히 행 수가 많을수록).

where 절의 조건

   WHERE A.A_Var8_FK_LK = @Param1

내부 조인 A로 이동해야합니다. 옵티마이 저는 내 경험상이 작업을 수행하기에 영리하지 못하며 (죄송합니다. 계획을 보지 못했습니다) 큰 차이를 만들 수 있습니다.

이러한 변경 사항이 개선되지 않으면 다음으로 A의 다른 임시 테이블과 A.A_Var8_FK_LK = @ Param1에 의해 제한 된 A에 대한 모든 임시 테이블을 만듭니다.

그런 다음 다음 조인 조건에 대해 해당 임시 테이블 (만들기 전 또는 후에)에 클러스터형 인덱스를 만듭니다.

그런 다음 그 결과를 남아있는 몇 개의 테이블 (F 및 T)에 조인하십시오.

Bam (행 추정치가 꺼져 있고 어쨌든 쉽게 개선 할 수 없는 경우에는 유능한 쿼리 계획이 필요함 ). 나는 당신이 적절한 계획을 가지고 있다고 가정하고 있는데, 그것은 내가 계획 내에서 먼저 확인할 것입니다.

추적은 급격한 영향을 줄 수도 있고 그렇지 않을 수도있는 tempdb 유출을 표시 할 수 있습니다.

최소한 시도하는 것이 더 빠른 또 다른 대안은 가장 적은 수의 행 (A)에서 가장 높은 수로 테이블을 정렬 한 다음 조인에 병합, 해시 및 루프를 추가하는 것입니다. 힌트가 있으면 결합 순서가 지정된대로 고정됩니다. 상대 행 수가 급격히 변하면 장기적으로 상처를 입을 수 있기 때문에 다른 사용자는 현명하게이 방법을 사용하지 않습니다. 최소한의 힌트가 바람직합니다.

이 중 많은 작업을 수행하는 경우 상용 최적화 프로그램을 사용해 보거나 시험해 볼 가치가 있으며 여전히 좋은 학습 경험입니다.


네 그렇습니다. A에 의해 리턴 된 행이 제한 조건에 의해 제한되도록합니다. 그렇지 않으면 옵티마이 저가 먼저 결합하여 나중에 제한 조건을 적용 할 수 있습니다. 나는 이것을 매일 다루고 있습니다.
crokusek

4
@crokusek 당신은 잘못되었습니다. SQL-Server의 옵티마이 저는 INNER 조인일 때 쿼리가 동등한 지 (조건이 WHERE 또는 ON 절에 있는지) 알 수 있습니다.
ypercubeᵀᴹ

6
Query Optimiser에서 Paul White의 시리즈가 유용 할 수 있습니다.
Martin Smith

끔찍한 습관입니다. 어쩌면이 특별한 경우 (한 가지 제약이있는 경우) 있지만 where 절에서 AND 조건을 쌓는 여러 개발자의 땅에서 왔습니다. SQL Server 계속 조인으로 다시 "이동" 하지 않습니다 .
crokusek

외부 (및 오른쪽 조인)에 대해 올바르지 않은 동의 그러나 where 절 내에 AND의 표현식 있고 각 용어 가 특정 내부 조인 에만 해당하는 경우 해당 용어는 최적화 및 모범 사례 (imo)로서 "온"위치로 안전하고 자신있게 이동 될 수 있습니다. "참"조인 조건인지 아니면 고정 구속 조건인지 여부는 큰 성능 향상에 따라 부차적입니다. 이 링크는 사소한 경우입니다. 실제 생활에는 convert ()와 수학을 사용하는 여러 조건이 있으며,이를 통해 모범 사례를 도출 할 수있는 더 나은 후보자가됩니다.
crokusek
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.