스캔이이 술어를 찾는 것보다 빠른 이유는 무엇입니까?


30

예상치 못한 것으로 설명되는 쿼리 성능 문제를 재현 할 수있었습니다. 내부에 중점을 둔 답변을 찾고 있습니다.

내 컴퓨터에서 다음 쿼리는 클러스터형 인덱스 스캔을 수행하며 약 6.8 초의 CPU 시간이 걸립니다.

SELECT ID1, ID2
FROM two_col_key_test WITH (FORCESCAN)
WHERE ID1 NOT IN
(
N'1', N'2',N'3', N'4', N'5',
N'6', N'7', N'8', N'9', N'10',
N'11', N'12',N'13', N'14', N'15',
N'16', N'17', N'18', N'19', N'20'
)
AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
ORDER BY ID1, ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY
OPTION (MAXDOP 1);

다음 쿼리는 클러스터 된 인덱스 탐색을 수행하지만 (차이 만이 FORCESCAN힌트를 제거하는 것임) 약 18.2 초의 CPU 시간이 걸립니다.

SELECT ID1, ID2
FROM two_col_key_test
WHERE ID1 NOT IN
(
N'1', N'2',N'3', N'4', N'5',
N'6', N'7', N'8', N'9', N'10',
N'11', N'12',N'13', N'14', N'15',
N'16', N'17', N'18', N'19', N'20'
)
AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
ORDER BY ID1, ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY
OPTION (MAXDOP 1);

쿼리 계획은 매우 비슷합니다. 두 쿼리 모두 클러스터 된 인덱스에서 120000001 개의 행을 읽습니다.

쿼리 계획

SQL Server 2017 CU 10을 사용하고 있습니다. two_col_key_test 테이블 .

drop table if exists dbo.two_col_key_test;

CREATE TABLE dbo.two_col_key_test (
    ID1 NVARCHAR(50) NOT NULL,
    ID2 NVARCHAR(50) NOT NULL,
    FILLER NVARCHAR(50),
    PRIMARY KEY (ID1, ID2)
);

DROP TABLE IF EXISTS #t;

SELECT TOP (4000) 0 ID INTO #t
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);


INSERT INTO dbo.two_col_key_test WITH (TABLOCK)
SELECT N'FILLER TEXT' + CASE WHEN ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) > 8000000 THEN N' 2' ELSE N'' END
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, NULL
FROM #t t1
CROSS JOIN #t t2;

콜 스택보고보다 더 많은 답변을 원합니다. 예를 들어sqlmin!TCValSSInRowExprFilter<231,0,0>::GetDataX 빠른 쿼리와 비교할 때 느린 쿼리에서 CPU주기가 훨씬 더 많이 걸린다 .

견해

거기서 멈추지 않고 그게 무엇인지, 왜 두 쿼리 사이에 큰 차이가 있는지 이해하고 싶습니다.

이 두 쿼리에 대해 CPU 시간에 큰 차이가있는 이유는 무엇입니까?

답변:


31

이 두 쿼리에 대해 CPU 시간에 큰 차이가있는 이유는 무엇입니까?

스캔 계획은 각 행에 대해 다음의 푸시 할 수없는 (잔여) 술어를 평가합니다.

[two_col_key_test].[ID1]<>N'1' 
AND [two_col_key_test].[ID1]<>N'10' 
AND [two_col_key_test].[ID1]<>N'11' 
AND [two_col_key_test].[ID1]<>N'12' 
AND [two_col_key_test].[ID1]<>N'13' 
AND [two_col_key_test].[ID1]<>N'14' 
AND [two_col_key_test].[ID1]<>N'15' 
AND [two_col_key_test].[ID1]<>N'16' 
AND [two_col_key_test].[ID1]<>N'17' 
AND [two_col_key_test].[ID1]<>N'18' 
AND [two_col_key_test].[ID1]<>N'19' 
AND [two_col_key_test].[ID1]<>N'2' 
AND [two_col_key_test].[ID1]<>N'20' 
AND [two_col_key_test].[ID1]<>N'3' 
AND [two_col_key_test].[ID1]<>N'4' 
AND [two_col_key_test].[ID1]<>N'5' 
AND [two_col_key_test].[ID1]<>N'6' 
AND [two_col_key_test].[ID1]<>N'7' 
AND [two_col_key_test].[ID1]<>N'8' 
AND [two_col_key_test].[ID1]<>N'9' 
AND 
(
    [two_col_key_test].[ID1]=N'FILLER TEXT' 
    AND [two_col_key_test].[ID2]>=N'' 
    OR [two_col_key_test].[ID1]>N'FILLER TEXT'
)

스캔 잔차

탐색 계획은 두 가지 탐색 작업을 수행합니다.

Seek Keys[1]: 
    Prefix: 
    [two_col_key_test].ID1 = Scalar Operator(N'FILLER TEXT'), 
        Start: [two_col_key_test].ID2 >= Scalar Operator(N'')
Seek Keys[1]: 
    Start: [two_col_key_test].ID1 > Scalar Operator(N'FILLER TEXT')

...이 술어 부분과 일치합니다.

(ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))

잔여 조건자는 위의 탐색 조건을 통과하는 행 (예제의 모든 행)에 적용됩니다.

그러나 각 불평등은 다음 보다 OR 큰 경우에 대해 두 개의 개별 테스트로 대체됩니다 .

([two_col_key_test].[ID1]<N'1' OR [two_col_key_test].[ID1]>N'1') 
AND ([two_col_key_test].[ID1]<N'10' OR [two_col_key_test].[ID1]>N'10') 
AND ([two_col_key_test].[ID1]<N'11' OR [two_col_key_test].[ID1]>N'11') 
AND ([two_col_key_test].[ID1]<N'12' OR [two_col_key_test].[ID1]>N'12') 
AND ([two_col_key_test].[ID1]<N'13' OR [two_col_key_test].[ID1]>N'13') 
AND ([two_col_key_test].[ID1]<N'14' OR [two_col_key_test].[ID1]>N'14') 
AND ([two_col_key_test].[ID1]<N'15' OR [two_col_key_test].[ID1]>N'15') 
AND ([two_col_key_test].[ID1]<N'16' OR [two_col_key_test].[ID1]>N'16') 
AND ([two_col_key_test].[ID1]<N'17' OR [two_col_key_test].[ID1]>N'17') 
AND ([two_col_key_test].[ID1]<N'18' OR [two_col_key_test].[ID1]>N'18') 
AND ([two_col_key_test].[ID1]<N'19' OR [two_col_key_test].[ID1]>N'19') 
AND ([two_col_key_test].[ID1]<N'2' OR [two_col_key_test].[ID1]>N'2') 
AND ([two_col_key_test].[ID1]<N'20' OR [two_col_key_test].[ID1]>N'20') 
AND ([two_col_key_test].[ID1]<N'3' OR [two_col_key_test].[ID1]>N'3') 
AND ([two_col_key_test].[ID1]<N'4' OR [two_col_key_test].[ID1]>N'4') 
AND ([two_col_key_test].[ID1]<N'5' OR [two_col_key_test].[ID1]>N'5') 
AND ([two_col_key_test].[ID1]<N'6' OR [two_col_key_test].[ID1]>N'6') 
AND ([two_col_key_test].[ID1]<N'7' OR [two_col_key_test].[ID1]>N'7') 
AND ([two_col_key_test].[ID1]<N'8' OR [two_col_key_test].[ID1]>N'8') 
AND ([two_col_key_test].[ID1]<N'9' OR [two_col_key_test].[ID1]>N'9')

잔여 물을 찾으십시오

각 불평등을 다시 작성 : 예 :

[ID1] <> N'1'  ->  [ID1]<N'1' OR [ID1]>N'1'

... 여기서는 비생산적입니다. 데이터 정렬 인식 문자열 비교는 비쌉니다. 비교 횟수를 두 배로 늘리면 대부분의 CPU 시간 차이가 설명됩니다.

문서화되지 않은 추적 플래그 9130을 사용하여 비 포괄 가능한 술어 푸시를 사용 불가능하게하면이를보다 명확하게 볼 수 있습니다. 그러면 잔차가 별도의 필터로 표시되며 별도로 검사 할 수있는 성능 정보가 표시됩니다.

주사

목표물 탐색

또한 검색시 약간의 카디널리티가 잘못 추정 될 수 있는데, 이는 옵티마이 저가 처음에 스캔보다 검색을 선택한 이유를 설명합니다 (검색 부분이 일부 행을 제거 할 것으로 예상 함).

부등식 재 작성은 (b- 트리 인덱스의 탐색 능력을 최대한 활용하기 위해) 인덱스 일치를 가능하게 할 수 있지만 (두 번에 두 개가 모두 잔차로 끝나는 경우)이 확장을 되 돌리는 것이 좋습니다. 이것을 SQL Server 피드백 사이트 에서 개선 된 것으로 제안 할 수 있습니다 .

또한 원래 ( "레거시") 카디널리티 추정 모델은이 쿼리에 대해 기본적으로 스캔을 선택합니다.

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