TOP 1을 추가하면 성능이 크게 저하되는 이유는 무엇입니까?


39

나는 매우 간단한 질문이 있습니다

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

그것은 끔찍한 성능을 제공합니다 (완료 될 때까지 기다리지 않는 것처럼). 쿼리 계획은 다음과 같습니다.

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

그러나 내가 제거하면 TOP 1다음과 같은 계획이 있으며 1-2 초 안에 실행됩니다.

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

아래의 올바른 PK 및 색인 생성.

(가) 사실 TOP 1나를 놀라게하지 않는 쿼리 계획을 변경, 나는 단지 조금 그것이 훨씬 더 악화 놀라입니다.

참고 :이 게시물 의 결과를 읽고 Row Goal등 의 개념을 이해했습니다 . 궁금한 점은 더 나은 계획을 사용하도록 쿼리를 변경하는 방법에 대해 궁금합니다. 현재 데이터를 임시 테이블에 덤프 한 다음 첫 번째 행을 가져옵니다. 더 좋은 방법이 있는지 궁금합니다.

편집 사실 여기에서 읽은 사람들에게는 몇 가지 추가 정보가 있습니다.

  • Document_Queue-PK / CI는 D_ID이며 ~ 5k 개의 행이 있습니다.
  • Correspondence_Journal-PK / CI는 FILE_NUMBER, CORRESPONDENCE_ID이며 ~ 1.4mil 행을 갖습니다.

내가 시작했을 때 다른 색인은 없었습니다. 나는 Correspondence_Journal (Document_Id, File_Number)에 하나가되었습니다.


1
당신이 적용하는 외래 키 제약 조건이 있습니까 DOCUMENT_ID두 테이블 사이의 관계를 (또는 모든 레코드는 않습니다 CORRESPONDENCE_JOURNAL일치하는 레코드를을 DOCUMENT_QUEUE)?
Daniel Hutmacher

답변:


28

해시 조인 강제 시도 *

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

옵티마이 저는 아마도 루프가 상위 1에서 더 나아질 것이라고 생각했을 것입니다. 그런 의미가 있지만 실제로는 작동하지 않습니다. 여기서 추측하지만 스풀의 예상 비용이 꺼져 있습니다. TEMPDB를 사용합니다. TEMPDB의 성능이 좋지 않을 수 있습니다.


* 조인 힌트 는 계획 테이블 액세스 순서가 쿼리에서 테이블 의 작성된 순서 와 일치하도록 ( OPTION (FORCE ORDER)지정된 것처럼) 강제하므로 조인 힌트에 주의하십시오 . 문서 링크에서 :

BOL 추출물

이 예제에서 바람직하지 않은 효과를 생성 할 수 있지만 일반적으로 매우 좋습니다. FORCE ORDER(묵시적 또는 명시 적)이있다 매우 순서를 시행 넘어 강력한 힌트; 부분 집계 및 재정렬을 포함하여 광범위한 옵티 마이저 기술이 적용되는 것을 방지합니다.

이 경우 암시하지 않기 때문에 적절한 경우 OPTION (HASH JOIN) 쿼리 힌트가 덜 방해적일 수 있습니다 FORCE ORDER. 그러나 쿼리의 모든 조인에는 적용됩니다. 다른 솔루션도 제공됩니다.


1
정답처럼 보이고 간단한 계획과의 유일한 차이점은 앞에 추가 정렬이었습니다.
Kenneth Fisher

3
이 답변이 마음에 들지 않습니다. 조인 힌트는 매우 침습적입니다. 날짜 열의 색인과 같이 간단한 색인 변경을 먼저 시도해야합니다.
usr

@usr 1 초 안에 실행되는 간단한 PK 조인입니다. 꽤 안전한 내기.
paparazzo

4
해시 조인을 강제 할 때는 큰 테이블을 강제로 스캔하게됩니다. 더 나은 옵션이 있습니다.
Rob Farley

30

를 사용하여 올바른 계획을 세웠 기 때문에 ORDER BY자신의 TOP운영자를 롤링 할 수 있습니까?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;

내 마음에, 위의 쿼리 계획은 ROW_NUMBER()당신이 같은 것처럼해야합니다 ORDER BY. 쿼리 계획에는 이제 세그먼트, 시퀀스 프로젝트 및 마지막으로 필터 연산자가 있어야하며 나머지는 올바른 계획과 같아야합니다.


3
실제로 최상위 연산자 (및 다른 많은 것들 (시퀀스 프로젝트, 세그먼트 및 정렬))를 제공했지만 여전히 초 단위로 실행되었습니다. @frisbee가 첫 번째 이래로 더 간단하지만 정답을 줄 것입니다. 그래도 좋은 대답입니다.
Kenneth Fisher

10
frisbee의 대답은 @KennethFisher가 더 간단하지만, 슬레지 해머는 표준 프레임 해머보다 더 간단하게 마무리 못을 구동합니다. 특히 장거리 운송을 위해 남겨두면 많은 위험이 따릅니다. 테스트 또는 어쩌면 프린지 예외를 제외하고는 힌트를 사용하지 않을 것입니다.
Steve Mangiameli

@SteveMangiameli이 특정한 경우에는 하나의 조인 만 있으므로 여러 가지 문제가 사라집니다. 조인 힌트 (또는 쿼리 힌트)를 사용하는 위험을 알고 있습니다.이 경우 정당하다고 생각합니다.
Kenneth Fisher

5
@KennethFisher Imo, 쿼리 힌트 의 주요 위험은 데이터가 커지거나 변경 될 때 적용하는 쿼리 계획이 시스템 자체에서 찾은 것보다 나빠질 수 있다는 것입니다. 계획의 작은 실수가 성능에 심각한 영향을 줄 수있는 방법을 이미 알고 있습니다. 생산에 힌트가 선언되고, 사용 "이 계획은 항상 것 내가 알고, 항상 내가 그렇게 완전히 계획을 이해하고 내 데이터는 생산이 쿼리의 수명을 통해 동작하는 방법 때문에 최선을 다합니다." 나는 쿼리에 대해 그렇게 확신하지 못했습니다.
jpmc26

29

편집 : +1은 FILE_NUMBER정수가 0으로 채워진 문자열 버전 이기 때문에이 상황에서 작동 합니다. 문자열에 대한 더 나은 해결책 ''은 값을 추가하면 순서에 영향을 줄 수 있거나 숫자는 상수이지만 결정적이지 않은 함수를 포함하는 것을 추가하기 위해 (빈 문자열)을 추가하는 것입니다 sign(rand()+1). '정렬을 깨는'아이디어는 여전히 유효합니다. 제 방법이 이상적이지 않은 것입니다.

+1

아니, 나는 내가 어떤 것에 동의한다는 것을 의미하지 않는다. 나는 그것을 해결책으로 의미한다. 쿼리를 ORDER BY cj.FILE_NUMBER + 1로 변경 TOP 1하면 동작이 다르게 작동합니다.

정렬 된 쿼리에 작은 행 목표를 설정하면 시스템은 정렬 연산자를 사용하지 않기 위해 데이터를 순서대로 사용하려고합니다. 또한 첫 번째 행을 찾기 위해 너무 많은 작업을 수행 할 필요가 없다는 것을 파악하여 해시 테이블을 작성하지 않아도됩니다. 귀하의 경우, 이것은 화살표의 두께에서 잘못된 것입니다. 단일 일치를 찾기 위해 많은 데이터를 소비 해야하는 것처럼 보입니다.

이 화살표의 두께는 DOCUMENT_QUEUE(DQ) 테이블이 CORRESPONDENCE_JOURNAL(CJ) 테이블 보다 훨씬 작음을 나타 냅니다. 그리고 가장 좋은 계획은 실제로 CJ 행을 찾을 때까지 DQ 행을 확인하는 것입니다. 실제로, 그것은 쿼리 최적화 프로그램 (QO)이 성가신 것이 없다면 ORDER BYCJ의 취재 지수에 의해 잘 지원됩니다.

따라서 ORDER BY완전히 삭제하면 Nested Loop와 관련된 계획을 얻을 것으로 예상됩니다 .DQ의 행을 반복하고 CJ가 행이 존재하는지 확인하려고합니다. 그리고로 TOP 1단일 행을 가져온 후에 중지됩니다.

그러나 실제로 첫 번째 행이 필요하면 FILE_NUMBER시스템을 속여서 도움이되는 것으로 보이는 색인을 무시하도록 속일 수 있습니다. ORDER BY CJ.FILE_NUMBER+1우리는 이전과 동일한 순서를 유지하지만 QO 하지 않습니다. QO는 전체 N 설정에 집중하여 Top N Sort 연산자를 만족시킬 수 있습니다. 이 방법은 순서 값을 계산하기위한 Compute Scalar 연산자와 첫 번째 행을 가져 오는 Top N Sort 연산자가 포함 된 계획을 생성해야합니다. 그러나 이것들의 오른쪽에는 CJ에서 많은 탐색을하는 멋진 Nested Loop가 표시되어야합니다. DQ와 일치하지 않는 큰 행 테이블을 실행하는 것보다 성능이 향상되었습니다.

해시 일치가 반드시 끔찍한 것은 아니지만 DQ에서 반환하는 행 집합이 CJ보다 훨씬 작 으면 해시 일치가 CJ를 더 많이 스캔합니다. 필요한 것보다

참고 : 쿼리 최적화 프로그램은 +0이 아무것도 변경하지 않는다는 것을 인식하기 때문에 +0 ​​대신 +1을 사용했습니다. 물론, 지금이 아니라면 미래에도 어느 시점에서 +1에도 동일한 내용이 적용될 수 있습니다.


7

이 게시물의 결과를 읽고 행 목표 등의 개념을 이해했습니다. 궁금한 점은 더 나은 계획을 사용하도록 쿼리를 변경하는 방법에 대한 것입니다.

추가 OPTION (QUERYTRACEON 4138)하면 최종 계획에 대해 지나치게 규정하지 않고 해당 쿼리에 대해서만 행 목표의 효과가 해제되며 아마도 가장 단순하고 직접적인 방법 일 것입니다.

이 힌트를 추가하면 권한 오류 (필수 DBCC TRACEON) 가 발생 하면 계획 지침을 사용하여 적용 할 수 있습니다.

사용 QUERYTRACEON계획 지침에 의해 spaghettidba

... 또는 저장 프로 시저를 사용하십시오.

어떤 권한이 QUERYTRACEON필요합니까? 에 의해 켄드라 리틀


3

최신 버전의 SQL Server는 옵티마이 저가 행 목표 최적화를 적용 할 수있을 때 차선의 성능을 얻는 쿼리를 처리하기위한 서로 다른 (그리고 아마도 더 나은) 옵션을 제공합니다. SQL Server 2016 SP1 DISABLE_OPTIMIZER_ROWGOAL USE HINT은 추적 플래그 4138과 동일한 효과를 제공합니다. 해당 버전이 아닌 경우 OPTIMIZE FOR쿼리 힌트를 사용하여 1 대신 모든 행을 반환하도록 설계된 쿼리 계획을 얻을 수도 있습니다. 아래 쿼리 는 질문의 결과와 동일한 결과를 반환하지만 단 1 행을 얻는 것을 목표로 생성되지는 않습니다.

DECLARE @top INT = 1;

SELECT TOP (@top) dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
OPTION (OPTIMIZE FOR (@top = 987654321));

2

을 (를) 수행하고 있으므로 시작을 결정하는 TOP(1)것이 좋습니다 ORDER BY. 최소한 이것은 기능적으로 예측 가능한 결과를 보장합니다 (항상 회귀 테스트에 유용함). 추가해야 할 것 같습니다 DC.D_ID그리고 CJ.CORRESPONDENCE_ID그것을 위해.

쿼리 계획을 볼 때 때로는 쿼리를 단순화하는 것이 유익하다는 것을 알았습니다 . QUEUE_DATE및 관련 카디널리티 추정 문제를 제거하기 위해 모든 관련 dc 행을 임시 테이블로 미리 선택하십시오 PRINT_LOCATION. 행 수가 적을수록 빠릅니다. 그런 다음 필요한 경우 영구 테이블을 변경하지 않고이 임시 테이블에 인덱스를 추가 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.