내장 된 select 문보다 오래 걸리는 경우


35

다음 코드를 실행하면 22.5 분이 걸리고 106million 읽기가 수행됩니다. 그러나 내부 select 문만 실행하면 15 초 밖에 걸리지 않으며 264k를 읽습니다. 참고로, 선택 쿼리는 레코드를 반환하지 않습니다.

IF EXISTS그렇게 오래 걸리고 더 많은 읽기를 할 수 있을지 알고 있습니까? 또한 select 문을 변경하고 SELECT TOP 1 [dlc].[id]2 분 후에 죽였습니다.

임시 수정으로 count (*)를 수행하고 해당 값을 variable에 할당하도록 변경했습니다 @cnt. 그런 다음 IF 0 <> @cnt진술을합니다. 그러나 EXISTSselect 문에 반환 된 레코드가 있으면 적어도 하나의 레코드를 찾으면 스캔 / 검색 수행을 중지하는 반면 count(*)전체 쿼리를 완료 하기 때문에 더 좋을 것이라고 생각 했습니다 . 내가 무엇을 놓치고 있습니까?

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
행 목표 문제를 피하기 위해 또 다른 아이디어 (추심하지 마십시오!)는 역을 시도하는 것 IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END입니다.
Aaron Bertrand

답변:


32

IF EXISTS그렇게 오래 걸리고 더 많은 읽기를 할 수 있을지 알고 있습니까? 또한 select 문을 변경하고 SELECT TOP 1 [dlc].[id]2 분 후에 죽였습니다.

이 관련 질문에 대한 답변에서 설명했듯이 :

TOP은 실행 계획에 어떤 영향을 미칩니 까?

를 사용 EXISTS하면 행 목표가 도입됩니다. 여기서 옵티마이 저는 첫 번째 행을 빠르게 찾기위한 실행 계획을 생성합니다. 이를 수행 할 때 데이터가 고르게 분포되어 있다고 가정합니다. 예를 들어 통계에 100,000 개의 행에 100 개의 예상 일치 항목이있는 경우 첫 번째 일치 항목을 찾으려면 1,000 개의 행만 읽어야한다고 가정합니다.

이 가정에 결함이있는 경우 예상 실행 시간보다 길어집니다. 예를 들어, SQL Server가 검색에서 가장 늦은 첫 번째 일치 값을 찾는 액세스 방법 (예 : 정렬되지 않은 스캔)을 선택하면 거의 완전한 스캔이 발생할 수 있습니다. 반면, 처음 몇 행에서 일치하는 행이 발견되면 성능이 매우 좋아집니다. 일관성없는 성능-행 목표의 근본적인 위험입니다.

임시 수정으로 count (*)를 수행하고 해당 값을 변수에 할당하도록 변경했습니다.

일반적으로 행 목표가 지정되지 않도록 쿼리를 재구성 할 수 있습니다. 행 목표가 없으면 일치하는 첫 번째 행이 발견 될 때 (정확하게 작성된 경우) 쿼리가 계속 종료 될 수 있지만 실행 계획 전략이 다를 수 있습니다 (바람직하게는 더 효과적 임). 분명히 count (*)는 모든 행을 읽어야하므로 완벽한 대안이 아닙니다.

SQL Server 2008 R2 이상을 실행하는 경우 일반적으로 문서화되고 지원되는 추적 플래그 4138 을 사용하여 행 목표없이 실행 계획을 얻을 수 있습니다. 계획 안내서와 함께 사용하지 않는 한이 플래그는 지원되는 hint를 사용하여 지정할 수도 OPTION (QUERYTRACEON 4138)있지만 런타임 sysadmin 권한 이 필요합니다 .

운수 나쁘게

위의 어느 것도 IF EXISTS조건문 과 함께 작동하지 않습니다. 일반 DML에만 적용됩니다. 그것은 것입니다 대체 작업 SELECT TOP (1)이 시도 배합. COUNT(*)앞에서 언급 한 것처럼 모든 정규화 된 행을 계산해야하는를 사용 하는 것보다 낫습니다 .

즉, 검색을 조기에 종료하면서 행 목표를 피하거나 제어 할 수있는이 요구 사항을 표현하는 방법에는 여러 가지가 있습니다. 마지막 예 :

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

제공 한 대체 예제는 3.75 분 안에 실행되었으며 46m 읽기를 수행했습니다. 따라서 원래 쿼리보다 빠르지 만이 경우 @cnt = count (*)를 고수하고 나중에 변수를 평가할 것이라고 생각합니다. 특히이 시간의 99 % 이후에는 아무것도 없습니다. 그것은 당신이 실제로 어떤 종류의 결과를 기대하고 그 결과가 데이터 내에 고르게 분포되어 있다면 Exists가 좋다는 당신과 Rob의 대답에 근거한 것처럼 들립니다.
Chris Woods

3
@ChrisWoods : 당신은 "특히 99 % 이후에는 아무 것도 없을 것"이라고 말했습니다. 이것은 일반적으로 행이 없을 것으로 예상하고 존재하지 않는 것을 찾기 위해 모든 것을 스캔해야하기 때문에 하나의 행 목표가 나쁜 생각임을 보장합니다. 영리한 색인을 추가 할 수 없으면 COUNT (*)를 사용하십시오.
Ross Presser

25

EXISTS는 단일 행만 찾으면되므로 행 목표는 1을 사용합니다. 이것은 때때로 이상적이지 않은 계획을 만들 수 있습니다. 당신이 그것을 좋아할 것이라고 기대한다면, a의 결과로 변수를 채우고 COUNT(*)그 변수가 0보다 큰지 테스트하십시오.

그래서 ... 작은 행 목표를 사용하면 해시 테이블 작성 또는 병합 조인에 유용 할 수있는 정렬 흐름과 같은 작업 차단을 피할 수 있습니다. 매우 빠르게 무언가를 찾아야하므로 중첩 루프는 뭔가를 찾으면 최고입니다. 이것을 제외하고는 전체 세트에서 훨씬 더 나쁜 계획을 만들 수 있습니다. 단일 행을 찾는 것이 빠르면 블록을 피하기 위해이 방법을 원합니다 ...

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