알 수없는 매개 변수 스니핑 vs 변수 vs 재 컴파일 vs 최적화


40

그래서 오늘 아침에 문제를 일으키는 긴 실행 프록이있었습니다 (30 초 + 실행 시간). 우리는 매개 변수 스니핑이 책임이 있는지 확인하기로 결정했습니다. 그래서 우리는 proc을 다시 작성하고 매개 변수 스니핑을 물리 치기 위해 들어오는 매개 변수를 변수로 설정했습니다. 시도 / 진정한 접근. Bam, 쿼리 시간이 향상되었습니다 (1 초 미만). 쿼리 계획을 볼 때 원본이 사용하지 않은 인덱스에서 개선 사항이 발견되었습니다.

오 탐지를 얻지 못했음을 확인하기 위해 원래 proc에서 dbcc freeproccache를 수행하고 개선 된 결과가 동일한 지 확인하기 위해 다시 실행했습니다. 그러나 놀랍게도 원래 프로세스는 여전히 느리게 진행되었습니다. 우리는 WITH RECOMPILE로 다시 시도했지만 여전히 느립니다 (프로세스에 대한 호출과 proc 내부에서 재 컴파일을 시도했습니다). 우리는 서버를 다시 시작했습니다 (명확히 dev 상자).

그래서 내 질문은 이것입니다 ... 빈 계획 캐시에서 동일한 느린 쿼리를 얻을 때 매개 변수 스니핑이 어떻게 비난받을 수 있습니까 ... snif에 매개 변수가 없어야합니까 ???

대신 계획 캐시와 관련이없는 테이블 통계의 영향을 받고 있습니까? 그렇다면 왜 들어오는 매개 변수를 변수로 설정하면 도움이됩니까?

추가 테스트에서 proc DID 내부에 OPTION (OPTIMIZE FOR UNKNOWN)을 삽입하면 계획이 개선 된 것으로 나타났습니다 .

그래서 여러분 중 일부는 나보다 똑똑합니다. 이런 유형의 결과를 내기 위해 무대 뒤에서 무슨 일이 일어나고 있는지에 대한 단서를 줄 수 있습니까?

또 다른 참고로, 느린 계획도 이유와 함께 GoodEnoughPlanFound일찍 중단되며 빠른 계획은 실제 계획에서 조기 중단 이유가 없습니다.

요약하자면

  • 수신 파라미터에서 변수 생성 (1 초)
  • 재 컴파일 (30 초 이상)
  • dbcc freeproccache (30 초 이상)
  • 옵션 (영국에서는 최적화) (1 초)

최신 정보:

느린 실행 계획은 여기를 참조하십시오 : https://www.dropbox.com/s/cmx2lrsea8q8mr6/plan_slow.xml

빠른 실행 계획은 https://www.dropbox.com/s/b28x6a01w7dxsed/plan_fast.xml을 참조 하십시오.

참고 : 테이블, 스키마, 개체 이름은 보안상의 이유로 변경되었습니다.

답변:


43

쿼리는

SELECT SUM(Amount) AS SummaryTotal
FROM   PDetail WITH(NOLOCK)
WHERE  ClientID = @merchid
       AND PostedDate BETWEEN @datebegin AND @dateend 

테이블에는 103,129,000 개의 행이 있습니다.

빠른 계획은 날짜에 잔류 술어가있는 ClientId가 조회하지만을 검색하려면 96 개의 조회를 수행해야합니다 Amount. <ParameterList>계획 의 섹션은 다음과 같습니다.

        <ParameterList>
          <ColumnReference Column="@dateend" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@datebegin" 
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@merchid" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

느린 계획은 날짜별로 조회되며 ClientId의 잔여 술어를 평가하고 금액을 검색하기 위해 조회합니다 (예상 1 대 실제 7,388,383). <ParameterList>섹션입니다

        <ParameterList>
          <ColumnReference Column="@EndDate" 
                           ParameterCompiledValue="'2013-02-01 23:59:00.000'" 
                           ParameterRuntimeValue="'2013-02-01 23:59:00.000'" />
          <ColumnReference Column="@BeginDate" 
                           ParameterCompiledValue="'2013-01-01 00:00:00.000'"               
                           ParameterRuntimeValue="'2013-01-01 00:00:00.000'" />
          <ColumnReference Column="@ClientID" 
                           ParameterCompiledValue="(78155)" 
                           ParameterRuntimeValue="(78155)" />
        </ParameterList>

두 번째 경우에는 ParameterCompiledValue것입니다 하지 빈. SQL Server가 쿼리에 사용 된 값을 스니핑했습니다.

"SQL Server 2005 실용 문제 해결" 책 에는 로컬 변수 사용에 대한 내용이 있습니다.

매개 변수 스니핑을 물리 치기 위해 로컬 변수를 사용하는 것은 상당히 일반적인 트릭이지만 힌트 OPTION (RECOMPILE)OPTION (OPTIMIZE FOR)힌트는 일반적으로보다 우아하고 덜 위험합니다.


노트

SQL Server 2005에서 명령문 수준 컴파일을 사용하면 쿼리를 처음 실행하기 직전까지 저장 프로 시저의 개별 명령문 컴파일을 연기 할 수 있습니다. 그때까지 지역 변수의 값을 알 수 있습니다. 이론적으로 SQL Server는이를 활용하여 매개 변수를 스니핑하는 것과 같은 방식으로 로컬 변수 값을 스니핑 할 수 있습니다. 그러나 SQL Server 7.0 및 SQL Server 2000+에서 매개 변수 스니핑을 막기 위해 로컬 변수를 사용하는 것이 일반적이기 때문에 SQL Server 2005에서는 로컬 변수 스니핑을 사용할 수 없었습니다. 향후 SQL Server 릴리스에서 사용 가능할 수도 있습니다. 선택의 여지가 있다면이 장에 요약 된 다른 옵션 중 하나를 사용해야하는 이유.


빠른 테스트 에서이 목적은 2008 년과 2012 년에도 동일하게 유지되며 지연 컴파일을 위해 변수가 스니핑되지 않고 명시 적 OPTION RECOMPILE힌트가 사용될 때만 스니핑 됩니다.

DECLARE @N INT = 0

CREATE TABLE #T ( I INT );

/*Reference to #T means this statement is subject to deferred compile*/
SELECT *
FROM   master..spt_values
WHERE  number = @N
       AND EXISTS(SELECT COUNT(*) FROM #T)

SELECT *
FROM   master..spt_values
WHERE  number = @N
OPTION (RECOMPILE)

DROP TABLE #T 

지연된 컴파일에도 불구하고 변수가 스니핑되지 않고 예상 행 수가 정확하지 않습니다.

견적 대 실제

따라서 느린 계획은 매개 변수화 된 버전의 쿼리와 관련이 있다고 가정합니다.

ParameterCompiledValue같다 ParameterRuntimeValue이 (계획 값이어서 다른 세트의 실행을위한 한 세트의 값에 대한 컴파일 된) 스니핑 대표적인 파라미터가되지 않도록 모든 파라미터에 대한.

문제는 올바른 매개 변수 값을 위해 컴파일 된 계획이 부적절하다는 것입니다.

여기여기에 설명 된 오름차순으로 문제가 발생했을 수 있습니다 . 1 억 개의 행이있는 테이블의 경우 SQL Server가 자동으로 통계를 업데이트하기 전에 2 천만을 삽입하거나 수정해야합니다. 지난 번에 업데이트 된 행은 쿼리의 날짜 범위와 일치했지만 현재는 7 백만입니다.

보다 빈번한 통계 업데이트를 예약 2389 - 90하거나 추적 플래그를 고려 하거나 사용 OPTIMIZE FOR UKNOWN하여 datetime열 에서 현재 오해의 소지가있는 통계를 사용할 수있는 것보다는 추측으로 돌아갑니다 .

다음 버전의 SQL Server (2012 이후)에는 필요하지 않을 수 있습니다. 관련 연결 항목은 흥미로운 반응을 포함

Microsoft가 2012 년 8 월 28 일 오후 1시 35 분에 게시 함
우리는이 문제를 근본적으로 해결하는 다음 주요 릴리스에 대한 카디널리티 추정 향상을 수행했습니다. 미리보기가 나오면 자세한 내용을 확인하십시오. 에릭

이 2014의 개선점은 Benjamin Nevarez가 기사의 끝 부분을 살펴 보았습니다.

새 SQL 서버 리티 견적에서의 첫 봐 .

새로운 카디널리티 추정기는이 경우 1 행 추정값을 제공하는 대신 폴백하고 평균 밀도를 사용하는 것으로 보입니다.

2014 카디널리티 추정기 및 오름차순 주요 문제에 대한 추가 정보는 다음과 같습니다.

SQL Server 2014의 새로운 기능 – 2 부 – 새로운 카디널리티 예상


29

내 질문은 이것입니다 ... 빈 계획 캐시에서 동일한 느린 쿼리를 얻을 때 매개 변수 스니핑이 어떻게 비난받을 수 있습니까? 스니핑 할 매개 변수가 없어야합니까?

SQL Server가 매개 변수 값을 포함하는 쿼리를 컴파일 할 때, 킁킁 카디널리티 (행 수) 추정에 이러한 매개 변수의 특정 값을 설정합니다. 귀하의 경우에는,의 특정 값 @BeginDate, @EndDate@ClientID실행 계획을 선택할 때 사용됩니다. 파라미터 스니핑에 대한 자세한 내용은 여기여기를 참조하십시오 . 위의 질문으로 인해 현재 개념이 불완전하게 이해된다고 생각하기 때문에 이러한 배경 링크를 제공하고 있습니다. 계획이 컴파일 될 때 스니핑해야 할 매개 변수 값이 항상 있습니다.

어쨌든, Martin Smith가 지적한 것처럼 매개 변수 스니핑은 여기서 문제가되지 않기 때문에 요점의 모든 것입니다. 느린 쿼리가 컴파일 될 때 통계는 @BeginDate및 의 스니핑 된 값에 대한 행이 없음을 나타냅니다 @EndDate.

느린 계획 스니핑 된 값

스니핑 된 값은 매우 최근에 마틴이 언급 ​​한 주요 문제를 암시 합니다. 날짜의 인덱스 탐색은 단 하나의 행만 리턴하는 것으로 추정되므로 옵티마이 저는 술어를 ClientID키 찾아보기 연산자에 잔차로 푸시하는 계획을 선택합니다 .

단일 행 추정값은 또한 옵티마이 저가 더 나은 계획을 찾는 것을 중지하고 충분한 계획 발견 메시지를 리턴하는 이유이기도합니다. 단일 행 추정으로 느린 계획의 예상 총 비용은 0.013136 비용 단위이므로 더 나은 것을 찾으려고 노력할 필요가 없습니다. 물론 seek는 실제로 하나가 아닌 7,388,383 개의 행을 반환하므로 동일한 수의 키 조회가 발생합니다.

통계는 최신 테이블을 유지하는 데 까다로울 수 있으며 큰 테이블에서 유용하며 파티셔닝 으로 인해 자체적 인 문제 가 발생합니다. 나는 추적 플래그 2389와 2390으로 특별한 성공을 거두지 못했지만 테스트를 환영합니다. 최신 SQL Server 빌드 (R2 SP1 이상)에는 동적 통계 업데이트가 있지만 파티션 별 통계 업데이트 는 아직 구현되지 않았습니다. 그 동안이 테이블을 크게 변경할 때마다 수동 통계 업데이트를 예약 할 수 있습니다.

이 특정 쿼리의 경우 빠른 쿼리 계획을 컴파일하는 동안 옵티마이 저가 제안한 인덱스를 구현하는 방법에 대해 생각합니다.

/*
The Query Processor estimates that implementing the following index could improve
the query cost by 98.8091%.

WARNING: This is only an estimate, and the Query Processor is making this 
recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide impact,
including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index>]
ON [dbo].[PDetail] ([ClientID],[PostedDate])
INCLUDE ([Amount]);

인덱스는 ON PartitionSchemeName (PostedDate)절 과 함께 파티션 정렬되어야 하지만 요점은 명백히 최상의 데이터 액세스 경로를 제공하면 최적화 프로그램이 OPTIMIZE FOR UNKNOWN힌트 나 로컬 변수를 사용하는 것과 같은 구식 해결 방법을 사용 하지 않고도 계획을 잘못 선택하지 않도록하는 데 도움이된다는 것 입니다.

향상된 인덱스를 사용하면 Amount열 을 검색하는 키 조회 가 제거되고 쿼리 프로세서는 여전히 동적 파티션 제거를 수행하고 탐색을 사용하여 특정 ClientID및 날짜 범위 를 찾을 수 있습니다.


두 가지 답변을 올바른 것으로 표시 할 수 있기를 바랍니다.하지만 추가 정보 덕분에 다시 한 번 감사드립니다. 매우 유익합니다.
RThomas

1
이 글을 게시한지 몇 년이 지났지 만 ... 알려 드리고 싶었습니다. 나는 여전히 끔찍한 시간을 "완전히 이해했다"라는 용어를 사용하며, 내가 할 때 항상 폴 화이트를 생각합니다. 매번 저를 움직입니다.
RThomas

0

저장 프로 시저가 느려진 것과 동일한 문제가 OPTIMIZE FOR UNKNOWN있었고 RECOMPILE쿼리 힌트가 느려짐을 해결하고 실행 시간을 단축했습니다. 그러나 다음 두 가지 방법은 저장 프로 시저의 속도 저하에 영향을 미치지 않았습니다. (i) 캐시 지우기 (ii) WITH RECOMPILE을 사용하여. 그래서, 당신이 말했듯이, 그것은 실제로 매개 변수 스니핑이 아닙니다.

추적 플래그 2389와 2390도 도움이되지 않았습니다. 통계 ( EXEC sp_updatestats)를 업데이트하면 나에게 도움이됩니다.

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