이 결합 카디널리티 추정이 왜 그렇게 큰가요?


18

다음 쿼리에 대한 카디널리티 예상치가 매우 높다고 생각되는 것을 경험하고 있습니다.

SELECT dm.PRIMARY_ID
FROM
(
    SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

예상 계획은 여기에 있습니다 . 실제 계획을 포함시킬 수 없도록 테이블의 통계 사본을 작성 중입니다. 그러나이 문제와 관련이 있다고 생각하지 않습니다.

SQL Server는 "dm"파생 테이블에서 481577 개의 행이 반환 될 것으로 예상합니다. 그런 다음 X_LAST_TABLE에 조인이 수행 된 후 4528030000 개의 행이 리턴 될 것으로 예상되지만 JOIN_ID는 X_LAST_TIME의 기본 키입니다. 0에서 481577 행 사이의 조인 카디널리티 추정이 예상됩니다. 대신 행 추정치가 외부 테이블과 내부 테이블을 교차 결합 할 때 얻을 수있는 행 수의 10 % 인 것으로 보입니다. 이것에 대한 수학은 반올림으로 작동합니다. 481577 * 94025 * 0.1 = 45280277425는 4528030000으로 반올림됩니다.

나는 주로이 행동의 근본 원인을 찾고 있습니다. 간단한 해결 방법에도 관심이 있지만 데이터 모델을 변경하거나 임시 테이블을 사용하지 않는 것이 좋습니다. 이 쿼리는 뷰 내에서 로직을 단순화 한 것입니다. 몇 개의 열에서 COALESCE를 수행하고 결합하는 것은 좋은 습관이 아니라는 것을 알고 있습니다. 이 질문의 목표 중 일부는 데이터 모델을 재 설계하도록 권장해야하는지 파악하는 것입니다.

레거시 카디널리티 추정기가 활성화 된 Microsoft SQL Server 2014에서 테스트 중입니다. TF 4199와 다른 사람들이 켜져 있습니다. 관련이있는 경우 추적 플래그의 전체 목록을 제공 할 수 있습니다.

가장 관련성이 높은 테이블 정의는 다음과 같습니다.

CREATE TABLE X_LAST_TABLE (
JOIN_ID NUMERIC(18, 0) NOT NULL
    CONSTRAINT PK_X_LAST_TABLE PRIMARY KEY CLUSTERED (JOIN_ID ASC)
);

또한 서버 중 하나에서 문제를 재현하려는 경우 통계와 함께 모든 테이블 작성 스크립트를 스크립팅했습니다 .

내 관측치 중 일부를 추가하기 위해 TF 2312를 사용하면 추정치가 수정되지만 이것이 나에게는 옵션이 아닙니다. TF 2301은 추정치를 수정하지 않습니다. 표 중 하나를 제거하면 추정값이 수정됩니다. 기 묘하게도 X_DETAIL_LINK의 조인 순서를 변경하면 추정치가 수정됩니다. 조인 순서를 변경하면 쿼리를 다시 작성하고 조인 순서를 힌트로 강요하지 않습니다. 다음은 조인 순서를 변경할 때 예상되는 쿼리 계획 입니다.


PS 만약 당신이 할 수있는 어떤 방식으로 스위치에 bigint대신의 decimal(18, 0)혜택을 얻을 것이다 당신 : 영향을 미칠 수 1) 사용 8 바이트 대신에 모든 값 9, 2) 대신 압축 된 데이터 형식의 바이트 비교 데이터 형식을 사용합니다, 값을 비교할 때 CPU 시간.
ErikE

@ErikE 팁 주셔서 감사하지만 이미 알고 있습니다. 불행하게도 우리는 레거시 이유로 BIGINT보다 NUMERIC (18,0)에 갇혀 있습니다.
Joe Obbish

그것은 가치가 있었다!
ErikE

에 null이 아닌 경우 X_DETAIL2X_DETAIL3테이블 이 필요 합니까? JOIN_IDX_DETAIL1
ErikE

@ErikE 이것은 MCVE이므로이 시점에서 쿼리가 정확하게 이해되지 않습니다.
Joe Obbish 2012 년

답변:


14

나는 COALESCE몇 개의 열에서 작업하고 열에 참여하는 것이 좋은 습관이 아니라는 것을 알고 있습니다.

스키마가 3NF + (키 및 제약 조건 포함)이고 쿼리가 관계형이고 주로 SPJG (선택-투영-조인 그룹 기준) 인 경우 우수한 카디널리티 및 분포 추정값을 생성하기는 어렵습니다. CE 모델은 이러한 원칙을 기반으로합니다. 쿼리에서 이례적 이거나 비 관계형 기능 이 많을수록 카디널리티 및 선택성 프레임 워크가 처리 할 수있는 범위에 더 가깝습니다. 너무 멀리 가서 CE는 포기하고 추측 합니다.

대부분의 MCVE 예는 단순한 SPJ (No G)이지만, 단순한 내부 말 조인 (또는 세미 조인)이 아닌 외부 말초 (내부 조인 + 안티-세미 조인으로 모델링 됨)가 있습니다. 외래 키나 다른 제약 조건은 없지만 모든 관계에는 키가 있습니다. 조인 중 하나를 제외하고 모두 일대 다이며, 좋습니다.

와 사이의 다 대다 외부 조인 은 예외입니다 . MCVE에서이 조인의 유일한 기능은의 행을 잠재적으로 복제하는 것입니다 . 이것은 특이한 일입니다.X_DETAIL_1X_DETAIL_LINKX_DETAIL_1

단순 등식 술어 (선택) 및 스칼라 연산자도 좋습니다. 예를 들어 attribute compare-equal attribute / constant는 일반적으로 모델에서 잘 작동합니다. 이러한 술어의 적용을 반영하도록 히스토그램 및 빈도 통계를 수정하는 것은 비교적 "쉽습니다".

COALESCE에 내장되어 CASE있으며 내부적으로 구현됩니다 IIF(이것은 IIFTransact-SQL 언어로 나타나기 전에 사실이었습니다 ). CE 모델 IIFUNION두 개의 상호 배타적 인 자식으로 구성되며 각각 입력 관계에 대한 선택 프로젝트로 구성됩니다. 나열된 각 구성 요소는 모델을 지원하므로 구성이 비교적 간단합니다. 그럼에도 불구하고 하나의 계층 추상화가 많을수록 최종 결과의 정확성이 떨어지기 때문에 실행 계획이 클수록 안정성과 안정성이 떨어집니다.

ISNULL반면에 엔진 에는 고유 합니다. 더 이상 기본 구성 요소를 사용하여 빌드되지 않습니다. ISNULL예를 들어 히스토그램에 효과를 적용하는 것은 NULL값에 대한 단계를 바꾸는 것 (필요한 경우 압축 )만큼 간단 합니다. 스칼라 연산자가 진행됨에 따라 여전히 불투명하므로 가능한 경우 피하는 것이 가장 좋습니다. 그럼에도 불구하고 일반적으로 CASE기반 대체 제품 보다 최적화 프로그램에 덜 적합합니다 (최적화에 적합하지 않음) .

CE (70 및 120+)는 SQL Server 표준에 의해서도 매우 복잡합니다. 각 연산자에 간단한 논리 (비밀 수식 포함)를 적용하는 경우는 아닙니다. CE는 키와 기능적 종속성에 대해 알고 있습니다. 빈도, 다변량 통계 및 히스토그램을 사용하여 추정하는 방법을 알고 있습니다. 그리고 특별한 경우, 개선, 점검 및 균형 및 지원 구조가 절대적으로 있습니다. 예를 들어 여러 방법으로 결합 (빈도, 히스토그램)을 추정하고이 둘의 차이를 기반으로 결과 또는 조정을 결정합니다.

다루어야 할 마지막 기본 사항 : 초기 카디널리티 추정은 쿼리 트리의 모든 작업에 대해 아래에서 위로 실행됩니다. 리프 연산자에 대한 선택 성과 카디널리티가 먼저 파생됩니다 (기본 관계). 수정 된 히스토그램 및 밀도 / 주파수 정보는 상위 연산자를 위해 파생됩니다. 트리를 더 높이 올리면 오차가 누적되는 경향으로 추정 품질이 저하되는 경향이 있습니다.

이 단일 종합 종합 추정은 시작점을 제공하며, 최종 실행 계획을 고려하기 전에 발생합니다 (사소한 계획 컴파일 단계 이전에도 발생합니다). 이 시점의 쿼리 트리는 작성된 쿼리 형식의 쿼리를 상당히 밀접하게 반영하는 경향이 있습니다 (하위 쿼리는 제거되고 단순화는 적용됨).

초기 추정 직후 SQL Server는 휴리스틱 조인 재정렬을 수행합니다. 느슨하게 말하면 트리를 재정렬하여 작은 테이블과 높은 선택성 조인을 먼저 배치하려고합니다. 또한 외부 조인 및 교차 제품 전에 내부 조인을 배치하려고합니다. 그 기능은 광범위하지 않습니다. 그들의 노력은 철저하지 않다. 물리적 비용은 고려하지 않습니다 (아직 존재하지 않기 때문에 통계 정보 및 메타 데이터 정보 만 존재 함). 휴리스틱 재정렬은 단순한 내부 동등 트리에서 가장 성공적입니다. 비용 기반 최적화를위한 "더 나은"시작점을 제공하기 위해 존재합니다.

이 결합 카디널리티 추정이 왜 그렇게 큰가요?

MCVE는 "비정상적인"대부분 중복 된 다 대다 조인과 COALESCE술어에서 동등 조인을 갖습니다 . 운영자 트리에는 또한 내부 조인 last 가 있습니다. 휴리스틱 조인 순서는 트리를 더 선호하는 위치로 이동할 수 없습니다. 모든 스칼라와 투영을 제외하고 결합 트리는 다음과 같습니다.

LogOp_Join [ Card=4.52803e+009 ]
    LogOp_LeftOuterJoin [ Card=481577 ]
        LogOp_LeftOuterJoin [ Card=481577 ]
            LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_LeftOuterJoin [ Card=481577 ]
                LogOp_Get TBL: X_DRIVING_TABLE(alias TBL: dt) [ Card=481577 ]
                LogOp_Get TBL: X_DETAIL_1(alias TBL: d1) [ Card=70 ]
                LogOp_Get TBL: X_DETAIL_LINK(alias TBL: lnk) [ Card=47 ]
            LogOp_Get TBL: X_DETAIL_2(alias TBL: d2) X_DETAIL_2 [ Card=119 ]
        LogOp_Get TBL: X_DETAIL_3(alias TBL: d3) X_DETAIL_3 [ Card=281 ]
    LogOp_Get TBL: X_LAST_TABLE(alias TBL: lst) X_LAST_TABLE [ Card=94025 ]

잘못된 최종 추정치가 이미 적용되었습니다. Card=4.52803e+009배정 밀도 부동 소수점 값 4.5280277425e + 9 (소수점 4528027742.5)로 내부적으로 인쇄되어 저장됩니다.

원래 쿼리의 파생 테이블이 제거되었으며 프로젝션이 정규화되었습니다. 초기 카디널리티 및 선택성 추정이 수행 된 트리의 SQL 표현은 다음과 같습니다.

SELECT 
    PRIMARY_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)
FROM X_DRIVING_TABLE dt
LEFT OUTER JOIN X_DETAIL_1 d1
    ON dt.ID = d1.ID
LEFT OUTER JOIN X_DETAIL_LINK lnk 
    ON d1.LINK_ID = lnk.LINK_ID
LEFT OUTER JOIN X_DETAIL_2 d2 
    ON dt.ID = d2.ID
LEFT OUTER JOIN X_DETAIL_3 d3 
    ON dt.ID = d3.ID
INNER JOIN X_LAST_TABLE lst 
    ON lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)

(제외 적으로, 반복 COALESCE은 최종 계획에도 있습니다-최종 계산 스칼라에서 한 번, 내부 조인의 내부에서 한 번).

마지막 조인에 주목하십시오. 이 내부 결합은 정의 X_LAST_TABLE에 따라 선택 (결합 술어)이 lst.JOIN_ID = COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)적용 되는 정의 (카르테 시안 곱) 및 선행 결합 출력 입니다. 직교 곱의 카디널리티는 단순히 481577 * 94025 = 45280277425입니다.

이를 위해 술어의 선택성을 결정하고 적용해야합니다. 주요 정보에 대한 영향과 함께 불투명 확장 COALESCE트리 ( UNIONIIF, 기억) 의 조합은 초기 "비정상적인"대부분 중복 된 다 대다 외부 결합의 히스토그램 및 빈도를 도출하여 CE가이를 수행 할 수 없음을 의미합니다. 일반적인 방법으로 수용 가능한 추정치를 도출합니다.

결과적으로 추측 논리에 들어갑니다. 추측 논리는 "교육받은"추측과 "교육되지 않은"추측 알고리즘의 계층으로 시도되어 적당히 복잡합니다. 추측에 대한 더 나은 근거가 발견되지 않으면 모형은 최후의 추측을 사용합니다. 평등 비교의 경우 : sqllang!x_Selectivity_Equal= 고정 0.1 선택도 (10 % 추측) :

콜 스택

-- the moment of doom
movsd xmm0,mmword ptr [sqllang!x_Selectivity_Equal

결과는 직교 곱에서 0.1 선택성입니다. 481577 * 94025 * 0.1 = 4528027742.5 (~ 4.52803e + 009).

다시 작성

문제가있는 조인이 주석 처리 되면 고정 선택성 "최후의 추측"을 피할 수 있기 때문에 더 나은 추정치가 생성됩니다 (핵심 정보는 1-M 조인에 의해 유지됨). COALESCE결합 술어가 전혀 CE 친화적이 아니기 때문에 추정의 품질은 여전히 ​​신뢰도가 낮습니다 . 수정 된 추정치는 적어도 인간에게는 더 합리적으로 보인다고 생각합니다.

쿼리를 외부 조인으로 작성하여 X_DETAIL_LINK 마지막배치 하면 휴리스틱 재정렬은 쿼리를 최종 내부 조인으로 교체 할 수 X_LAST_TABLE있습니다. 내부가 바로 옆에 참여 퍼팅 문제의 외부 "특별한"는 대부분 중복의 효과 대다 외부 올 조인 이후, 초기 재주문의 제한된 능력을 최종 견적을 개선 할 수있는 기회를 제공 가입 까다로운 선택도 추정 에 대한 COALESCE. 다시 말하지만, 추정치는 고정 된 추측보다 조금 낫습니다. 그리고 아마도 법정에서 결정된 교차 검토를 견디지 ​​못할 것입니다.

내부 및 외부 조인을 재정렬하는 것은 어렵고 시간이 많이 걸립니다 (2 단계 완전 최적화조차도 이론적 움직임의 제한된 하위 집합 만 시도합니다).

ISNULLMax Vernon의 답변에 제안 된 중첩 은 구제 금융 고정 추측을 피하기 위해 관리되지만 최종 추정치는 불가능한 제로 행입니다 (정확도를 위해 한 행으로 향상). 이것은 계산이 가진 모든 통계적 기초에 대해 1 행의 고정 된 추측 일 수 있습니다.

0에서 481577 행 사이의 조인 카디널리티 추정이 예상됩니다.

이것은 물리적으로 다르지만 의미 적으로 동일한 서브 트리에서 카디널리티 추정이 다른 시간에 발생할 수 있음 (비용 기반 최적화 중)이 발생할 수 있음을 받아들 일지라도 합리적인 기대치입니다. (메모 그룹당) 최고. 계획 전체 일관성 보증이 없다고해서 개별 조인이 존중을 소중히 할 수 있어야한다는 의미는 아닙니다.

반면에 우리가 최후의 수단으로 추측 하면 희망은 이미 잃어 버렸으므로 왜 귀찮게합니까? 우리는 우리가 알고있는 모든 속임수를 시도하고 포기했습니다. 다른 것이 없다면, 최종 결과는이 쿼리를 컴파일하고 최적화하는 동안 모든 것이 CE 내부로 잘 들어 가지 않았다는 큰 경고 신호입니다.

MCVE를 시도했을 때 120+ CE ISNULL는 원래 쿼리에 대해 제로 (= 1) 행 최종 추정치를 생성했습니다. 이는 내 생각에 맞지 않습니다.

실제 솔루션에는 아마도 설계 변경이 포함되어있어 COALESCE또는 없이 또는 간단한 ISNULL키 조인을 허용하고 쿼리 컴파일에 유용한 외래 키 및 기타 제약 조건이 가능합니다.


10

나는 가입 한 Compute Scalar결과로 인한 운영자 가 문제의 근본 원인 이라고 생각합니다 . 역사적으로 컴퓨팅 스칼라는 정확히 1 , 2 비용을 내기가 어려웠습니다 .COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID)X_LAST_TABLE.JOIN_ID

당신이 (감사합니다!) 최소한의 완전하고 검증 예제를 제공 한 때문에 정확한 통계로, 나는 더 이상 참여하도록 쿼리를 다시 작성할 수 있어요는 필요 CASE기능이 COALESCE훨씬 더 정확 행 추정 결과에 확장을, 그리고 분명히 더 정확한 전체 비용 산정 마지막 부록을 참조하십시오. :

SELECT COALESCE(dm.d1ID, dm.d2ID, dm.d3ID)
FROM
(
    SELECT d1ID = d1.JOIN_ID
        , d2ID = d2.JOIN_ID
        , d3ID = d3.JOIN_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst 
    ON (dm.d1ID IS NOT NULL AND dm.d1ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NOT NULL AND dm.d2ID = lst.JOIN_ID)
    OR (dm.d1ID IS NULL AND dm.d2ID IS NULL AND dm.d3ID IS NOT NULL AND dm.d3ID = lst.JOIN_ID);

이 있지만 xID IS NOT NULL기술적으로 필요하지 않은 이후, ID = JOIN_ID널 (null) 값에 가입하지 않습니다 더 명확하게 의도를 묘사하기 때문에, 내가 그들을 포함되어 있습니다.

계획 1계획 2

계획 1 :

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

계획 2 :

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

새로운 쿼리는 병렬화의 이점을 제공합니다. 또한, 새로운 쿼리는 1의 추정 출력 행 수를 가지는데, 이는 실제로 원래 쿼리에 대한 추정치 4528030000보다 하루가 끝날 때 더 나쁠 수 있습니다. 새 쿼리에서 select 연산자의 하위 트리 비용은 243210에, 원본은 536.535에 있으며 이는 훨씬 적습니다. 나는 첫 번째 견적이 현실에 가까운 곳이라고 생각하지 않습니다.


부록 1.

@Lamak과의 토론 으로 The Heap ™의 다양한 사람들과 추가 상담을 한 후 , 위의 관찰 쿼리는 병렬 처리에서도 끔찍하게 수행되는 것으로 보입니다. 우수한 성능 카디널리티 추정을 모두 가능하게하는 솔루션은 다음 COALESCE(x,y,z)과 같이 ISNULL(ISNULL(x, y), z)를로 대체합니다 .

SELECT dm.PRIMARY_ID
FROM
(
    SELECT ISNULL(ISNULL(d1.JOIN_ID, d2.JOIN_ID), d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID
    LEFT OUTER JOIN X_DETAIL_2 d2 ON dt.ID = d2.ID
    LEFT OUTER JOIN X_DETAIL_3 d3 ON dt.ID = d3.ID
) dm
INNER JOIN X_LAST_TABLE lst ON dm.PRIMARY_ID = lst.JOIN_ID;

COALESCECASE쿼리 최적화 프로그램에서 "표지 아래"문 으로 변환됩니다 . 따라서 카디널리티 추정기는 내부에 묻혀있는 열에 대한 신뢰할 수있는 통계를 발견하기가 더 어렵습니다 COALESCE. ISNULL내장 함수 인 것은 카디널리티 추정기에 훨씬 "공개적"입니다. 또한 ISNULL대상이 널 입력 불가능한 것으로 알려진 경우 최적화 할 수있는 가치가 없습니다 .

ISNULL변형 계획은 다음과 같습니다.

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

(계획 버전을 여기에 붙여 넣으 십시오 ).

참고로, 위의 그래픽 계획을 작성하는 데 사용했던 훌륭한 계획 탐색기를 위해 Sentry One 에 소품을 제공 합니다.


-1

조인 조건에 따라 테이블은 여러 방식으로 정렬 될 수 있으며, "특정 방식으로 변경"하면 결과가 수정됩니다.

하나의 테이블 만 조인하면 올바른 결과를 얻을 수 있다고 가정하십시오.

SELECT COALESCE(d1.JOIN_ID, d2.JOIN_ID, d3.JOIN_ID) PRIMARY_ID
    FROM X_DRIVING_TABLE dt
    LEFT OUTER JOIN X_DETAIL_1 d1 ON dt.ID = d1.ID
    LEFT OUTER JOIN X_DETAIL_LINK lnk ON d1.LINK_ID = lnk.LINK_ID

대신에 또는 X_DETAIL_1중 하나를 사용할 수 있습니다 .X_DETAIL_2X_DETAIL_3

따라서 휴식 2 테이블의 목적은 명확하지 않습니다.

테이블 X_DETAIL_1을 두 부분으로 나누는 것과 같습니다 .

대부분의 아마 " 이. 당신이 그 테이블을 채우는하는 오류가 이상적" X_DETAIL_1, X_DETAIL_2X_DETAIL_3행의 동일한 금액을 포함해야합니다.

그러나 하나 이상의 테이블에 원하지 않는 수의 행이 있습니다.

내가 틀렸다면 죄송합니다.

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