고유 인덱스에 대한 (동일한) 1000 탐색의 예상 비용이이 계획에서 다른 이유는 무엇입니까?


27

아래 쿼리에서 두 실행 계획은 고유 인덱스에 대해 1,000 회의 검색을 수행하는 것으로 추정됩니다.

탐색은 동일한 소스 테이블에서 정렬 된 스캔에 의해 수행되므로 동일한 순서로 동일한 값을 찾는 것으로 보입니다.

두 개의 중첩 루프는 <NestedLoops Optimized="false" WithOrderedPrefetch="true">

왜이 작업이 첫 번째 계획에서는 0.172434이고 두 번째 계획에서는 3.01702의 비용이 드는지 아는 사람이 있습니까?

(질문의 이유는 첫 번째 쿼리가 계획 비용이 훨씬 낮기 때문에 최적화로 제안 되었기 때문입니다. 실제로 더 많은 작업을하는 것처럼 보이지만 불일치를 설명하려고합니다. .)

설정

CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);

CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL); 

INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,  
     master..spt_values v2;

INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;

쿼리 1 "계획 붙여 넣기"링크

WITH T
     AS (SELECT *
         FROM   Target AS T
         WHERE  T.KeyCol IN (SELECT S.KeyCol
                             FROM   Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol;

쿼리 2 "계획 붙여 넣기"링크

MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol; 

쿼리 1

쿼리 2

위의 내용은 SQL Server 2014 (SP2) (KB3171021)-12.0.5000.0 (X64)에서 테스트되었습니다.


@Joe Obbish 는 더 단순한 재현이 될 것이라고 의견에서 지적합니다.

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

vs

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN (SELECT * FROM Target) AS T 
    ON T.KeyCol = S.KeyCol;

의 모두 1,000 행 스테이징 테이블 위에서 정지 파생 테이블없이 중첩 루프와 동일한 평면 형상 및 평면이 나타나는 저렴하지만, 비용의 차이 위와 10,000 행 스테이징 테이블과 동일한 목표 테이블의 계획을 변경하지 이러한 비용 불일치가 계획을 비교하는 것을 어렵게하는 것 외에 다른 영향을 미칠 수 있음을 보여주는 형태 (전체 스캔 및 병합 조인이 비용이 많이 드는 검색보다 상대적으로 매력적으로 보입니다).

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

답변:


20

왜이 작업이 첫 번째 계획에서는 0.172434이고 두 번째 계획에서는 3.01702의 비용이 드는지 아는 사람이 있습니까?

일반적으로, 중첩 된 루프 결합 아래의 내부 탐색은 임의의 I / O 패턴을 가정하여 비용이 발생합니다. 이전 액세스에서 필요한 페이지가 이미 메모리로 가져 왔을 가능성을 고려하여 후속 액세스에 대한 간단한 대체 기반 감소가 있습니다. 이 기본 평가는 표준 (더 높은) 비용을 산출합니다.

Smart Seek Costing 이라는 또 다른 원가 계산 입력이 있는데 , 이에 대한 세부 사항은 알려져 있지 않습니다. 제 생각에는 SSC가 현지 주문 및 / 또는 가져올 값의 범위를 고려하여 내부 탐색 I / O 비용을 더 자세히 평가하려고 시도한다는 것입니다. 누가 알아.

예를 들어, 첫 번째 탐색 조작은 요청 된 행뿐만 아니라 해당 페이지의 모든 행 (인덱스 순서)을 가져옵니다. 전체 액세스 패턴을 고려할 때 1000 회 탐색에서 1000 행을 페치하려면 미리 읽기 및 프리 페치가 비활성화 된 경우에도 2 개의 물리적 읽기만 필요합니다. 이러한 관점에서 기본 I / O 원가 계산은 상당히 과대 평가되어 있으며 SSC 조정 비용은 현실에 더 가깝습니다.

루프가 인덱스 탐색을 직접적으로 또는 덜 탐색하게하는 경우 SSC가 가장 효과적 일 것으로 예상되며, 조인 외부 참조는 탐색 조작의 기본입니다. 내가 알 수 있듯이 SSC는 항상 적절한 물리적 작업을 시도하지만 탐색이 다른 작업에 의해 조인과 분리 될 때 하향 조정을하지 않는 경우가 많습니다. SQL Server가 종종 데이터 액세스 연산자로 이러한 필터를 푸시 할 수 있기 때문에 간단한 필터는 이에 대한 한 가지 예외입니다. 어쨌든 옵티마이 저는 선택을 매우 깊이 지원합니다.

하위 쿼리 외부 프로젝션에 대한 Compute Scalar가 여기서 SSC를 방해하는 것 같습니다. Compute Scalar는 일반적으로 조인 위에 재배치되지만이 위치는 그대로 있어야합니다. 그럼에도 불구하고 대부분의 일반적인 Compute Scalar는 최적화에 매우 투명하므로 약간 놀랍습니다.

어쨌든, PhyOp_Range인덱스 SelIdxToRng에서 간단한 선택으로 물리적 작업 이 생성 되면 SSC가 효과적입니다. 더 복잡한 SelToIdxStrategy(테이블에서 인덱스 전략에 대한 선택)을 사용하면 결과적 PhyOp_Range으로 SSC가 실행되지만 감소는 없습니다. 다시 말하지만, 더 단순하고 직접적인 작업은 SSC에서 가장 잘 작동하는 것 같습니다.

SSC가하는 일을 정확히 말하고 정확한 계산을 보여줄 수 있기를 바라지 만 자세한 내용은 모릅니다. 사용 가능한 제한된 추적 출력을 탐색하려는 경우 문서화되지 않은 추적 플래그 2398을 사용할 수 있습니다. 출력 예는 다음과 같습니다.

스마트 탐색 원가 계산 (7.1) :: 1.34078e + 154, 0.001

이 예는 비용 상한과 0.001의 계수를 보여주는 메모 그룹 7, 대안 1과 관련이 있습니다. 더 깨끗한 요소를 보려면 페이지가 최대한 조밀하도록 병렬 처리없이 테이블을 다시 작성하십시오. 그렇게하지 않으면 팩터는 예제 대상 테이블의 경우 0.000821과 비슷합니다. 물론 거기에는 상당히 명백한 관계가 있습니다.

문서화되지 않은 추적 플래그 2399를 사용하여 SSC를 비활성화 할 수도 있습니다.이 플래그를 활성화하면 두 비용이 모두 더 높습니다.


8

이것이 답인지는 확실하지 않지만 의견이 조금 길다. 차이의 원인은 내 입장에서 순수한 추측이며 다른 사람들에게 생각할 음식이 될 수 있습니다.

실행 계획이있는 단순화 된 쿼리

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

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

실제로 동일한 실행 계획을 초래할 수있는 이러한 동등한 쿼리의 주요 차이점은 계산 스칼라 연산자입니다. 왜 그것이 있어야하는지 모르겠지만 옵티마이 저가 파생 테이블을 최적화 할 수있는 한 추측합니다.

컴퓨팅 스칼라의 존재가 두 번째 쿼리의 IO 비용을 높이는 것이라고 생각합니다.

에서 계획 원가 계산 : 내부 옵티 마이저

CPU 비용은 첫 번째 행의 경우 0.0001581로, 후속 행의 경우 0.000011로 계산됩니다.
...
0.003125의 I / O 비용이 정확히 1/320이다 - 디스크 서브 시스템은 초당 320 랜덤 I / O 작업을 수행 할 수있는 모델의 가정을 반영
...
원가 계산 구성 요소의 총 수 있음을 인식하는 스마트 충분하다 디스크에서 가져와야하는 페이지는 전체 테이블을 저장하는 데 필요한 페이지 수를 초과 할 수 없습니다.

필자의 경우 테이블에는 5618 페이지가 걸리고 1000000 행에서 1000 개의 행을 얻으려면 필요한 페이지 수는 5.618이며 IO 비용은 0.015625입니다.

두 쿼리 솔기의 CPU 비용은 동일 0.0001581 * 1000 executions = 0.1581합니다.

위의 기사에 따르면 첫 번째 쿼리의 비용은 0.173725입니다.

그리고 계산 스칼라가 IO 비용을 어떻게 엉망으로 만드는지에 대해 정확하다고 가정하면 3.2831로 계산할 수 있습니다.

계획에 정확히 나와있는 것이 아니라 이웃에 있습니다.


6

(이것은 Paul의 답변에 대한 의견으로 더 나을 것이지만 아직 충분한 담당자가 없습니다.)

나는 DBCC앞으로 비슷한 불일치를 조사하는 것이 도움이 될 수 있도록, 거의 결론에 도달하기 위해 사용했던 추적 플래그 (및 몇 가지 진술) 목록을 제공하고 싶었다 . 이 모든 것을 프로덕션 환경에서 사용 해서는 안됩니다 .

먼저, Final Memo 를 살펴보고 어떤 물리 연산자가 사용되고 있는지 확인했습니다. 그래픽 실행 계획에 따라 분명히 동일하게 보입니다. 그래서, 추적 플래그를 사용 3604하고 8615는 먼저 클라이언트에 출력을 지정하고, 두 번째는 최종 메모를 보여준다 :

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

에서 추적 Root Group하여 거의 동일한 PhyOp_Range연산자를 찾았습니다 .

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

나에게 유일하게 명백한 차이점은 2.0and로 3.0, 각각의 "memo group 2, original"및 "memo group 3, original"을 나타냅니다. 메모를 확인하면 동일한 내용을 참조하므로 아직 차이점이 없습니다.

둘째로, 나는 결실이없는 것으로 입증 된 흔적 플래그의 혼란을 조사했지만 흥미로운 내용이 있습니다. 나는 Benjamin Nevarez 에서 가장 많이 들었다 . 한 경우에는 적용되었지만 다른 경우에는 적용되지 않은 최적화 규칙에 대한 실마리를 찾고있었습니다.

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

셋째, 나는 우리에게 어떤 규칙이 적용되는지 살펴 보았습니다 PhyOp_Range. 블로그 포스트 에서 Paul이 언급 한 몇 가지 추적 플래그를 사용했습니다 .

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

결과에서, JOIN이 규칙을 직접 적용하여 PhyOp_Range연산자 를 얻는 것을 볼 수 있습니다 : Rule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2). 부속 선택이 대신이 규칙을 적용했습니다 Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2).. 여기에서 각 규칙과 관련된 "스마트 탐색 원가 계산"정보가 표시됩니다. 직접- JOIN이것은 출력입니다 (나에게) : Smart seek costing (7.2) :: 1.34078e+154 , 0.001. 부속 선택의 경우 다음이 출력 Smart seek costing (9.2) :: 1.34078e+154 , 1됩니다..

결국, 나는 많은 결론을 내릴 수 없었습니다. 그러나 바울의 대답은 대부분의 격차를 좁 힙니다. 스마트 탐색 원가 계산에 대한 추가 정보를보고 싶습니다.


4

이것은 실제로 답이 아닙니다-Mikael이 지적 했듯이이 문제를 의견에서 논의하기가 어렵습니다 ...

흥미롭게도 하위 쿼리 (select KeyCol FROM Target)를 인라인 TVF로 변환하면 계획과 비용이 간단한 원래 쿼리와 동일하다는 것을 알 수 있습니다.

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

쿼리 계획 ( pastetheplan 링크 ) :

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

공제로 인해 원가 계산 엔진 이이 유형의 하위 쿼리가 미칠 수 있는 잠재적 영향 에 대해 혼란스러워한다고 생각합니다 .

예를 들면 다음과 같습니다.

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

어떻게 것입니다 당신은 그 비용? 쿼리 최적화 프로그램은 계산 스칼라 ( pastetheplan.com 링크 )를 포함하는 위의 "하위 쿼리"변형과 매우 유사한 계획을 선택합니다 .

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

계산 스칼라는 위에 표시된 "하위 쿼리"변형과 상당히 다른 비용을 갖지만 쿼리 최적화 프로그램은 반환 된 행의 수를 미리 알 수있는 방법이 없기 때문에 추측에 불과합니다. 행 추정값을 알 수 없으므로 대상 테이블의 행 수로 설정되므로 계획에서 왼쪽 외부 조인에 해시 일치를 사용합니다.

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

나는 미카엘이 그의 답변에서 한 일에 동의한다는 것을 제외하고는 이것으로부터 큰 결론을 얻지 못하고 다른 사람이 더 나은 답변을 얻을 수 있기를 바랍니다.

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