선언 된 조인 열 순서를 변경하면 정렬이 발생하는 이유는 무엇입니까?


40

이름이 동일하고 유형이 지정된 색인화 된 키 열이있는 두 개의 테이블이 있습니다. 그들 중 하나는 고유 한 클러스터형 인덱스를 가지고 있고 다른 하나는 고유하지 않은 인덱스를 가지고 있습니다.

테스트 설정

현실적인 통계를 포함한 설정 스크립트 :

DROP TABLE IF EXISTS #left;
DROP TABLE IF EXISTS #right;

CREATE TABLE #left (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE UNIQUE CLUSTERED INDEX IX ON #left (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #left WITH ROWCOUNT=63800000, PAGECOUNT=186000;

CREATE TABLE #right (
    a       char(4) NOT NULL,
    b       char(2) NOT NULL,
    c       varchar(13) NOT NULL,
    d       bit NOT NULL,
    e       char(4) NOT NULL,
    f       char(25) NULL,
    g       char(25) NOT NULL,
    h       char(25) NULL
    --- and a few other columns
);

CREATE CLUSTERED INDEX IX ON #right (a, b, c, d, e, f, g, h)

UPDATE STATISTICS #right WITH ROWCOUNT=55700000, PAGECOUNT=128000;

재현

클러스터링 키에서이 두 테이블을 조인하면 일대 다 MERGE 조인이 필요합니다.

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.a=r.a AND
    l.b=r.b AND
    l.c=r.c AND
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

이것은 내가 원하는 쿼리 계획입니다.

이것이 내가 원하는거야.

(경고를 신경 쓰지 말고 가짜 통계와 관련이 있습니다.)

그러나 조인에서 열 순서를 변경하면 다음과 같이됩니다.

SELECT *
FROM #left AS l
LEFT JOIN #right AS r ON
    l.c=r.c AND     -- used to be third
    l.a=r.a AND     -- used to be first
    l.b=r.b AND     -- used to be second
    l.d=r.d AND
    l.e=r.e AND
    l.f=r.f AND
    l.g=r.g AND
    l.h=r.h
WHERE l.a='2018';

...이 발생합니다 :

조인에서 선언 된 열 순서를 변경 한 후 쿼리 계획

Sort 연산자는 선언 된 조인 순서에 따라 스트림을 정렬하는 것 같습니다. 즉 c, a, b, d, e, f, g, h, 쿼리 계획에 차단 작업을 추가합니다.

내가 본 것들

  • 열을 NOT NULL동일한 결과 로 변경하려고했습니다 .
  • 원래 테이블은로 작성 ANSI_PADDING OFF되었지만 이 테이블을 작성 ANSI_PADDING ON해도이 계획에는 영향을 미치지 않습니다.
  • 나는 INNER JOIN대신에 LEFT JOIN변화를 시도했다 .
  • 2014 SP2 Enterprise에서 발견했으며 2017 개발자 (현재 CU)에서 재현을 만들었습니다.
  • 선행 색인 열에서 WHERE 절을 제거하면 올바른 계획이 생성되지만 결과에 영향을 미칩니다 .. :)

마지막으로, 우리는 질문에 도달

  • 이것은 의도적 인 것입니까?
  • 쿼리를 변경하지 않고 정렬을 제거 할 수 있습니까 (공급 업체 코드이므로 실제로는 아닙니다 ...). 테이블과 인덱스를 변경할 수 있습니다.

답변:


28

이것은 의도적 인 것입니까?

의도적으로 설계된 것입니다. Microsoft가 Connect 피드백 사이트를 폐기했을 때이 주장에 대한 최고의 공개 소스는 유감스럽게도 없어 졌기 때문에 SQL Server 팀의 개발자들로부터 많은 유용한 의견이 사라졌습니다.

어쨌든, 현재의 최적화 설계는하지 않습니다 적극적으로 추구 불필요한 종류 피하기 위해 그 자체를 . 이는 윈도우 기능 등에서 가장 자주 발생하지만 순서에 민감한 다른 연산자, 특히 연산자 간 순서를 유지하는 경우에도 볼 수 있습니다.

그럼에도 불구하고 옵티마이 저는 불필요한 정렬을 피하는 데 상당히 뛰어나지 만 (많은 경우)이 순서는 일반적으로 다른 순서 조합을 적극적으로 시도하는 것 이외의 이유로 발생합니다. 그런 의미에서 수용 가능한 비용으로 일반 계획 품질을 향상시키는 것으로 보여진 직교 옵티 마이저 기능 간의 복잡한 상호 작용에 관한 것이기 때문에 '검색 공간'에 대한 문제가 아닙니다.

예를 들어, 정렬 요구 사항 (예 : 최상위 레벨 ORDER BY)을 기존 색인과 일치시켜 정렬을 피할 수 있습니다 . 사소한 경우에는 추가를 의미 할 수 ORDER BY l.a, l.b, l.c, l.d, l.e, l.f, l.g, l.h;있지만 지나치게 단순화 된 것입니다 (쿼리를 변경하고 싶지 않기 때문에 받아 들일 수 없습니다).

보다 일반적으로, 각 메모 그룹은 입력 순서를 포함 할 수있는 필수 또는 원하는 특성과 연관 될 수있다. 특정 주문 을 시행해야하는 명백한 이유가없는 경우 (예 :을 충족 시키 ORDER BY거나 주문에 민감한 물리적 연산자의 정확한 결과를 보장하기 위해) '행운'요소가 있습니다. 병합 결합으로 정렬 피하기 에서 결합을 결합 (결합 또는 결합 모드로)하는 것과 관련하여 그 세부 사항에 대해 더 썼습니다 . 그 대부분은 제품의 지원 표면적을 넘어서므로 정보 용으로 취급하고 변경 될 수 있습니다.

특별한 경우, 예, jadarnel27 이 정렬을 피하기 위해 제안한대로 색인 조정할 수 있습니다 . 실제로 병합 조인을 선호하는 이유는 거의 없습니다. 또한 OPTION(HASH JOIN, LOOP JOIN)데이터에 대한 지식과 최고, 최악 및 평균 사례 성능 간의 균형에 따라 쿼리를 변경하지 않고 계획 가이드 를 사용하여 해시 또는 루프 물리적 조인 중에서 선택할 수 있습니다 .

마지막으로, 호기심으로, 복잡한 잔차와 함께 ORDER BY l.b잠재적으로 덜 효율적인 다 대다 병합 조인 비용으로 간단하게 정렬을 피할 수 있습니다 b. 필자는 앞에서 언급 한 옵티 마이저 기능과 최상위 요구 사항을 전파 할 수있는 방법 간의 상호 작용을 주로 설명합니다.


19

쿼리를 변경하지 않고 정렬을 제거 할 수 있습니까 (공급 업체 코드이므로 실제로는 아닙니다 ...). 테이블과 인덱스를 변경할 수 있습니다.

인덱스를 변경할 수 있으면 #right조인의 필터 순서와 일치하도록 인덱스 순서를 변경 하면 정렬이 제거됩니다 (나를 위해).

CREATE CLUSTERED INDEX IX ON #right (c, a, b, d, e, f, g, h)

놀랍게도 (적어도 나에게) 이것은 쿼리가 정렬로 끝나지 않습니다.

이것은 의도적 인 것입니까?

이상한 추적 플래그 의 출력을 보면 최종 메모 구조에 흥미로운 차이점이 있습니다.

각 쿼리에 대한 최종 메모 구조의 스크린 샷

상단의 "루트 그룹"에서 볼 수 있듯이 두 쿼리 모두이 쿼리를 실행하기위한 기본 물리적 작업으로 병합 조인을 사용하는 옵션이 있습니다.

좋은 쿼리

정렬이 없는 조인 은 그룹 29 옵션 1 및 그룹 31 옵션 1 (각각 관련된 인덱스에 대한 범위 스캔)에 의해 구동됩니다. 조인을 필터링하는 일련의 논리적 비교 작업 인 그룹 27 (표시되지 않음)로 필터링됩니다.

잘못된 쿼리

하나 정렬은 두 그룹 (29, 31) 각각이 보유하고있는 (신규) 옵션 (3)에 의해 구동된다. 옵션 3은 앞에서 언급 한 범위 스캔 결과 (각 그룹의 옵션 1)에 대한 물리적 정렬을 수행합니다.

왜?

어떤 이유로 두 번째 쿼리에서는 29.1 및 31.1을 병합 조인의 소스로 직접 사용하는 옵션을 옵티 마이저에서도 사용할 수 없습니다. 그렇지 않으면 다른 옵션 중 루트 그룹 아래에 나열 될 것이라고 생각합니다. 그것이 가능하다면 엄청나게 비싼 정렬 작업보다 더 많이 선택할 것입니다.

나는 다음 중 하나만 결론을 내릴 수 있습니다.

  • 이것은 옵티 마이저 검색 알고리즘의 버그 (또는 제한 사항 일 수 있음)
    • 인덱스와 조인을 5 개의 키만 갖도록 변경하면 두 번째 쿼리에 대한 정렬이 제거됩니다 (6, 7, 8 개의 키는 모두 정렬 됨).
    • 이는 8 개의 키가있는 검색 공간이 너무 커서 옵티마이 저가 비 정렬 솔루션을 실행 가능한 옵션으로 식별 할 시간이 없다는 것을 의미합니다.
    • 조인 조건의 순서가 옵티마이 저의 검색 프로세스에 많은 영향을 미친다는 것은 약간 버그가있는 것처럼 보이지만 실제로는 내 머리 위에 약간 있습니다.
  • 결과의 정확성을 보장하기 위해 정렬이 필요합니다.
    • 키가 적거나 키가 다른 순서로 지정된 경우 쿼리 정렬하지 않고 실행할 있기 때문에 이것은 가능성이 없어 보입니다.

누군가가 와서 정렬이 필요한지 설명 할 수 있기를 바랍니다 . 그러나 Memo 빌딩의 차이점은 답변으로 게시하기에 충분히 재미 있다고 생각했습니다.


1
검색 공간에 대한 귀하의 의견이 실제로 여기에 있다고 생각합니다. 인덱스 만 사용하기 위해서는 옵티마이 저가 조건에 적합한 지 확인해야합니다. 5 개가 지난 키는 폴백하기 전에 너무 많은 가능성을 점검해야합니다. 쿼리의 모든 순서의 조합을 최적화 가을에 다시 대에 성공 얼마나 많은, 열거한다면 나는 싶은데요
Mr.Mindor

예, 불일치가 약간 버그가있는 것처럼 보이지만 인덱스를 확인하는 데 사용되는 알고리즘에 전적으로 의존하는 것 같습니다. 모든 조합이 테스트 된 경우 결과에서 패턴을보고 사용되는 알고리즘을 결정할 수 있습니다. 더 일반적인 사용 사례에 최적으로 수행하도록 작성되었습니다. 시간 제한 내에서 8 개의 주요 솔루션을 안정적으로 찾을 수있는 대안이있을 수 있지만 3-4 개 미만의 키가있는 경우 현재 솔루션보다 느립니다.
Mr.Mindor
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.