SQL Server 2014 : 일관성없는 자체 조인 카디널리티 추정에 대한 설명이 있습니까?


27

SQL Server 2014에서 다음 쿼리 계획을 고려하십시오.

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

쿼리 계획에서 자체 조인 ar.fId = ar.fId은 1 행으로 추정됩니다. 그러나, 이것은 논리적으로 일관성이 추정은 다음과 같습니다 ar있다 20,608행의 한 별개의 값 fId(정확하게 통계에 반영을). 따라서이 조인은 행 ( ~424MM행) 의 전체 교차 곱을 생성하여 몇 시간 동안 쿼리를 실행합니다.

SQL Server가 통계와 일치하지 않는 것으로 쉽게 입증 될 수있는 추정치를 제시하는 이유를 이해하는 데 어려움을 겪고 있습니다. 어떤 아이디어?

초기 조사 및 추가 세부 사항

Paul의 대답에 따르면 조인 카디널리티를 추정하기위한 SQL 2012 및 SQL 2014 휴리스틱이 두 개의 동일한 히스토그램을 비교해야하는 상황을 쉽게 처리해야하는 것처럼 보입니다.

추적 플래그 2363의 출력으로 시작했지만 쉽게 이해할 수 없었습니다. 다음은 SQL 서버가 히스토그램을 비교하는 것을 의미 조각 하는가 fIdbIdA는 해당 용도에 가입의 선택을 추정하기 위해 fId? 그렇다면 분명히 맞지 않을 것입니다. 또는 추적 플래그 출력을 잘못 읽습니까?

Plan for computation:
  CSelCalcExpressionComparedToExpression( QCOL: [ar].fId x_cmpEq QCOL: [ar].fId )
Loaded histogram for column QCOL: [ar].bId from stats with id 3
Loaded histogram for column QCOL: [ar].fId from stats with id 1
Selectivity: 0

전체 repro 스크립트에 포함되어 있으며이 쿼리를 밀리 초로 가져 오는 몇 가지 해결 방법이 있습니다. 이 질문은 동작 이해, 향후 쿼리에서이를 피하는 방법 및 Microsoft에 제출해야하는 버그인지 확인하는 데 중점을 둡니다.

여기에 전체 repro 스크립트가 있습니다 . 여기에 추적 플래그 2363전체 출력이 있습니다. 여기에 전체 스크립트를 열지 않고 빠르게보고자하는 경우를위한 쿼리 및 테이블 정의가 있습니다.

WITH cte AS (
    SELECT ar.fId, 
        ar.bId,
        MIN(CONVERT(INT, ar.isT)) AS isT,
        MAX(CONVERT(INT, tcr.isS)) AS isS
    FROM  #SQL2014MinMaxAggregateCardinalityBug_ar ar 
    LEFT OUTER JOIN #SQL2014MinMaxAggregateCardinalityBug_tcr tcr
        ON tcr.rId = 508
        AND tcr.fId = ar.fId
        AND tcr.bId = ar.bId
    GROUP BY ar.fId, ar.bId
)
SELECT s.fId, s.bId, s.isS, t.isS
FROM cte s 
JOIN cte t 
    ON t.fId = s.fId 
    AND t.isT = 1

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_ar (
    fId INT NOT NULL,
    bId INT NOT NULL,
    isT BIT NOT NULL
    PRIMARY KEY (fId, bId)
)

CREATE TABLE #SQL2014MinMaxAggregateCardinalityBug_tcr (
    rId INT NOT NULL,
    fId INT NOT NULL,
    bId INT NOT NULL,
    isS BIT NOT NULL
    PRIMARY KEY (rId, fId, bId, isS)
)

답변:


23

SQL Server가 통계와 일치하지 않는 것으로 쉽게 입증 될 수있는 추정치를 제시하는 이유를 이해하는 데 어려움을 겪고 있습니다.

일관성

일관성에 대한 일반적인 보장은 없습니다. 추정치는 상이한 통계적 방법을 사용하여 상이한 시간에 상이한 (그러나 논리적으로 동등한) 서브 트리에서 상이한 시간에 계산 될 수있다.

이 두 개의 동일한 서브 트리를 결합하면 교차 곱을 생성해야한다고하는 논리에는 아무런 문제가 없지만 추론의 선택이 다른 것보다 더 건전하다고 말할 것도 없습니다.

초기 추정

특정한 경우, 조인에 대한 초기 카디널리티 추정은 두 개의 동일한 서브 트리에서 수행되지 않습니다 . 당시의 나무 모양은 다음과 같습니다.

  LogOp_Join
     LogOp_GbAgg
        LogOp_LeftOuterJoin
           LogOp_Get TBL : ar
           LogOp_Select
              LogOp_Get TBL : tcr
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [tcr] .rId
                 ScaOp_Const 값 = 508
           ScaOp_Logical x_lopAnd
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .fId
                 ScaOp_Identifier [tcr] .fId
              ScaOp_Comp x_cmpEq
                 ScaOp_Identifier [ar] .bId
                 ScaOp_Identifier [tcr] .bId
        AncOp_PrjList 
           AncOp_PrjEl Expr1003 
              ScaOp_AggFunc stopMax
                 ScaOp_Convert int
                    ScaOp_Identifier [tcr] .isS
     LogOp_Select
        LogOp_GbAgg
           LogOp_LeftOuterJoin
              LogOp_Get TBL : ar
              LogOp_Select
                 LogOp_Get TBL : tcr
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [tcr] .rId
                    ScaOp_Const 값 = 508
              ScaOp_Logical x_lopAnd
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .fId
                    ScaOp_Identifier [tcr] .fId
                 ScaOp_Comp x_cmpEq
                    ScaOp_Identifier [ar] .bId
                    ScaOp_Identifier [tcr] .bId
           AncOp_PrjList 
              AncOp_PrjEl Expr1006 
                 ScaOp_AggFunc stopMin
                    ScaOp_Convert int
                       ScaOp_Identifier [ar] .isT
              AncOp_PrjEl Expr1007 
                 ScaOp_AggFunc stopMax
                    ScaOp_Convert int
                       ScaOp_Identifier [tcr] .isS
        ScaOp_Comp x_cmpEq
           ScaOp_Identifier Expr1006 
           ScaOp_Const 값 = 1
     ScaOp_Comp x_cmpEq
        ScaOp_Identifier QCOL : [ar] .fId
        ScaOp_Identifier QCOL : [ar] .fId

제 조인 입력은, 투영 집합체 얻어 간략화 갖고, 상기 제 2 입력은 술어 가지고 가입 t.isT = 1하는 경우, 그 아래를 가압 t.isT이다 MIN(CONVERT(INT, ar.isT)). 그럼에도 불구하고 isT술어에 대한 선택성 계산 CSelCalcColumnInInterval은 히스토그램 에서 사용할 수 있습니다 .

  CSelCalcColumnInInterval
      칼럼 : COL : Expr1006 

열 QCOL에 대한로드 된 히스토그램 : ID가 3 인 통계에서 [ar] .isT

선택성 : 4.85248e-005

통계 수집 생성 : 
  CStCollFilter (ID = 11, CARD = 1)
      CStCollGroupBy (ID = 10, 카드 = 20608)
          CStCollOuterJoin (ID = 9, 카드 = 20608 x_jtLeftOuter)
              CStCollBaseTable (ID = 3, 카드 = 20608 TBL : ar)
              CStCollFilter (ID = 8, 카드 = 1)
                  CStCollBaseTable (ID = 4, 카드 = 28 TBL : tcr)

(정확한) 기대는이 술어에 의해 20,608 행이 1 행으로 줄어드는 것입니다.

조인 추정

이제 다른 조인 입력에서 20,608 개의 행이이 한 행과 어떻게 일치하는지 문제가됩니다.

  LogOp_Join
      CStCollGroupBy (ID = 7, 카드 = 20608)
          CStCollOuterJoin (ID = 6, 카드 = 20608 x_jtLeftOuter)
              ...

      CStCollFilter (ID = 11, CARD = 1)
          CStCollGroupBy (ID = 10, 카드 = 20608)
              ...

      ScaOp_Comp x_cmpEq
          ScaOp_Identifier QCOL : [ar] .fId
          ScaOp_Identifier QCOL : [ar] .fId

일반적으로 조인을 추정하는 방법에는 여러 가지가 있습니다. 예를 들면 다음과 같습니다.

  • 각 하위 트리의 각 계획 연산자에서 새 히스토그램을 도출하고 조인에 정렬하고 (필요에 따라 보간 단계 값 보간) 이들이 어떻게 일치하는지 확인합니다. 또는
  • 히스토그램의 간단한 '거친'정렬을 수행하십시오 (단계별이 아닌 최소값과 최대 값 사용). 또는
  • 기본 테이블에서 필터링없이 조인 열에 대해서만 별도의 선택성을 계산 한 다음 비조 인 술어의 선택성 효과를 추가하십시오.
  • ...

사용중인 카디널리티 추정기 및 일부 휴리스틱에 따라 이들 중 하나 (또는 ​​변형)를 사용할 수 있습니다. 자세한 내용은 SQL Server 2014 카디널리티 추정기쿼리 계획 최적화를 참조하십시오 .

곤충?

이제 질문에서 언급 했듯이이 경우 '단순'단일 열 조인 (on fId)은 CSelCalcExpressionComparedToExpression계산기를 사용합니다 .

계산 계획 :

  CSelCalcExpressionComparedToExpression [ar] .fId x_cmpEq [ar] .fId

열 QCOL에 대한로드 된 히스토그램 : [ar] .bID가 2 인 통계의 ID
열 QCOL에 대한로드 된 히스토그램 : ID가 1 인 통계의 [ar] .fId

선택도 : 0

이 계산은 필터링 된 1 개의 행으로 20,608 개의 행을 결합하면 선택성이 0이됩니다. 일치하는 행이 없습니다 (최종 계획에서 한 행으로보고 됨). 이것이 잘못 되었습니까? 네, 아마도 새 CE에 버그가있을 것입니다. 하나의 행이 모든 행과 일치하거나 전혀 일치하지 않는다고 주장 할 수 있으므로 결과는 합리적 일 수 있지만 그렇지 않은 이유가 있습니다.

세부 사항은 실제로 까다로울 수 있지만 fId, 필터의 선택도에 의해 수정되고 20608 * 20608 * 4.85248e-005 = 20608행을 제공하는 필터링 되지 않은 히스토그램을 기반으로 한 추정치 는 매우 합리적입니다.

이 계산을 따르는 것은 CSelCalcSimpleJoinWithDistinctCounts대신 계산기 를 사용하는 것을 의미합니다 CSelCalcExpressionComparedToExpression. 이를 수행하는 문서화 된 방법은 없지만 궁금한 점이 있으면 문서화되지 않은 추적 플래그 9479를 활성화 할 수 있습니다.

9479 계획

최종 조인은 두 개의 단일 행 입력에서 20,608 개의 행을 생성하지만 놀랄 일이 아닙니다. TF 9481에 따라 최초 CE에서 생산 한 것과 동일한 계획입니다.

나는 세부 사항이 까다 롭고 조사하는 데 시간이 많이 걸리는 것을 언급했지만 내가 알 수있는 한 문제의 근본 원인은 rId = 508선택성 이없는 술어와 관련이 있습니다. 이 제로 추정값은 일반적인 방식으로 한 행으로 올라가므로 입력 트리에서 더 낮은 술어를 설명 할 때 해당 조인에서 제로 선택성 추정값에 기여하는 것으로 보입니다 (따라서에 대한로드 통계 bId).

외부 조인이 하나의 행으로 올리지 않고 0 행 내부 측 추정값을 유지하도록 허용하면 (모든 외부 행이 규정 됨) 두 계산기를 사용하여 '버그가없는'조인 추정치가 제공됩니다. 이것을 탐구하고 싶다면, 문서화되지 않은 추적 플래그는 9473 (단독)입니다 :

9473 계획

결합 카디널리티 추정의 동작은 CSelCalcExpressionComparedToExpression문서화되지 않은 다른 변형 플래그로``bId ''를 설명하지 않도록 수정할 수도 있습니다 (9494). 나는 당신이 그런 것들에 관심이 있다는 것을 알고 있기 때문에이 모든 것을 언급합니다. 그들이 해결책을 제공하기 때문이 아닙니다. 문제를 Microsoft에보고하고 문제를 해결하기 전까지는 쿼리를 다르게 표현하는 것이 가장 좋은 방법 일 것입니다. 의도적 인 행동인지 아닌지에 관계없이 회귀에 대해 듣는 것에 관심을 가져야합니다.

마지막으로, 재생산 스크립트에서 언급 된 다른 것을 정리하기 위해 : 질문 계획에서 필터의 최종 위치 GbAggAfterJoinSel는 결합 출력이 작은 값을 가지므로 결합과 필터를 결합 위로 이동시키는 비용 기반 탐색의 결과입니다. 행 수 예상대로 필터가 처음에 결합 아래에있었습니다.

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