이 쿼리가 비 클러스터형 인덱스를 사용하지 않는 이유는 무엇이며 어떻게 만들 수 있습니까?


12

후속으로 이 질문에 쿼리 성능을 늘리는 방법에 대한, 나는 기본적으로 사용 내 인덱스를 만들 수있는 방법이 있는지 알고 싶습니다.

이 쿼리는 약 2.5 초 안에 실행됩니다.

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

이것은 약 33ms에서 실행됩니다.

SELECT TOP 1000 * FROM [CIA_WIZ].[dbo].[Heartbeats]
WHERE [DateEntered] BETWEEN '2011-08-30' and '2011-08-31' 
ORDER BY [DateEntered], [DeviceID];

[ID] 필드 (pk)에 클러스터 된 인덱스가 있고 [DateEntered], [DeviceID]에 클러스터되지 않은 인덱스가 있습니다. 첫 번째 쿼리는 클러스터 된 인덱스를 사용하고 두 번째 쿼리는 비 클러스터형 인덱스를 사용합니다. 내 질문은 두 부분입니다.

  • 왜 두 쿼리의 [DateEntered] 필드에 WHERE 절이 있기 때문에 서버가 첫 번째 클러스터 된 인덱스를 사용하지만 두 번째 쿼리는 사용하지 않습니까?
  • orderby 없이도이 쿼리에서 비 클러스터형 인덱스를 기본적으로 사용하려면 어떻게해야합니까? (또는 왜 그런 행동을 원하지 않습니까?)

DateEntered는 DateTime입니다.이 경우 날짜 부분을 사용하고 있지만 때로는 날짜와 시간을 함께 쿼리합니다.
Nate

답변:


9

첫 번째 쿼리는 앞에서 설명한 임계 값을 기준으로 테이블을 스캔 합니다. 수백만 개의 행이있는 좁은 테이블에서 쿼리 성능을 향상시킬 수 있습니까?

(구가없는 쿼리 TOP 1000는 46k 개 이상의 행을 반환하거나 35k ~ 46k 사이의 일부를 반환합니다 (회색 영역 ;-))

두 번째 쿼리는 주문해야합니다. NC 인덱스는 원하는 순서대로 정렬되므로 옵티마이 저가 해당 인덱스를 사용한 다음 클러스터 된 인덱스에 대한 책갈피 조회를 사용하여 누락 된 열을 클러스터 된 인덱스 스캔을 수행 한 후 클러스터링 된 인덱스 스캔을 수행 한 후 필요한 경우 그것을 주문하십시오.

ORDER BY절의 열 순서를 반대로 바꾸면 NC INDEX가 쓸모 없기 때문에 클러스터형 인덱스 스캔으로 돌아갑니다.

편집 두 번째 질문에 대한 답변을 잊어 버렸습니다. 왜 이것을 원하지 않습니까?

클러스터되지 않은 비 커버링 인덱스를 사용하면 NC 인덱스에서 rowID를 찾은 다음 클러스터 된 인덱스에서 누락 된 열을 찾아야합니다 (클러스터형 인덱스에는 테이블의 모든 열이 포함됨). 클러스터형 인덱스에서 누락 된 열을 조회하는 IO는 임의 IO입니다.

여기서 핵심은 RANDOM입니다. NC 인덱스에있는 모든 행에 대해 액세스 방법은 클러스터형 인덱스에서 새 페이지를 찾아야합니다. 이것은 임의적이므로 매우 비쌉니다.

반면에 옵티마이 저는 클러스터 된 인덱스 스캔을 수행 할 수도 있습니다. 할당 맵을 사용하여 스캔 범위를 조회하고 대규모 청크로 클러스터형 인덱스 읽기를 시작할 수 있습니다. 이것은 순차적이며 훨씬 저렴합니다. (테이블이 조각 나지 않는 한 :-) 단점은 WHOLE 클러스터형 인덱스를 읽어야한다는 것입니다. 이것은 버퍼와 잠재적으로 많은 양의 IO에 좋지 않습니다. 그러나 여전히 순차적 IO.

귀하의 경우, 옵티마이 저는 35k에서 46k 행 사이를 결정하므로 전체 클러스터 인덱스 스캔에 비해 저렴합니다. 그래, 틀렸어 그리고 선택적 WHERE절이나 큰 테이블이 아닌 좁은 비 클러스터형 인덱스가있는 많은 경우에 이것은 잘못됩니다. (너무 좁은 테이블이기 때문에 테이블이 더 나쁩니다.)

이제를 추가하면 ORDER BY전체 클러스터형 인덱스를 스캔 한 다음 결과를 정렬하는 데 비용이 더 많이 듭니다. 대신 옵티마이 저는 이미 주문한 NC 인덱스를 사용하는 것이 더 저렴하고 북마크 조회에 대해 임의 IO 페널티를 지불하는 것으로 가정합니다.

따라서 귀하의 주문은 완벽한 "쿼리 힌트"종류의 솔루션입니다. 그러나 특정 시점에서 쿼리 결과가 너무 크면 책갈피 조회 임의 IO에 대한 페널티가 너무 커져 느려집니다. 옵티마이 저가 그 시점 이전에 계획을 클러스터형 인덱스 스캔으로 다시 변경한다고 가정하지만 확실하지 않습니다.

귀하의 경우, 삽입물이 입력 날짜별로 주문되는 한, 채팅 및 이전 질문 (링크 참조)에서 설명한 것처럼, 입력 한 날짜 열에 클러스터형 인덱스를 작성하는 것이 좋습니다.


20

다른 구문을 사용하여 쿼리를 표현하면 비 클러스터형 인덱스를 사용하려는 최적화 프로그램에 의사를 전달하는 데 도움이 될 수 있습니다. 아래 양식을 통해 원하는 계획을 찾으십시오.

SELECT
    [ID],
    [DeviceID],
    [IsPUp],
    [IsWebUp],
    [IsPingUp],
    [DateEntered]
FROM [dbo].[Heartbeats]
WHERE
    [ID] IN
(
    -- Keys
    SELECT TOP (1000)
        [ID]
    FROM [dbo].[Heartbeats]
    WHERE 
        [DateEntered] >= CONVERT(datetime, '2011-08-30', 121)
        AND [DateEntered]  < CONVERT(datetime, '2011-08-31', 121)
);

쿼리 계획

비 클러스터형 인덱스에 힌트가있는 경우 생성 된 계획과 해당 계획을 비교하십시오.

SELECT TOP (1000) 
    * 
FROM [dbo].[Heartbeats] WITH (INDEX(CommonQueryIndex))
WHERE 
    [DateEntered] BETWEEN '2011-08-30' and '2011-08-31';

강제 색인 힌트 계획

계획은 본질적으로 동일합니다 (키 조회는 클러스터 된 인덱스에 대한 검색에 지나지 않습니다). 두 계획 양식 모두 비 클러스터형 인덱스에서 한 번의 검색 만 수행하고 클러스터형 인덱스에서 최대 1000 개의 조회를 수행합니다.

중요한 차이점은 최상위 연산자의 위치에 있습니다. 두 탐색 사이에 배치 된 Top은 옵티마이 저가 두 탐색 조작을 논리적으로 동등한 클러스터 색인 스캔으로 대체하지 못하게합니다. 옵티마이 저는 논리적 계획의 일부를 동등한 관계형 조작으로 대체하여 작동합니다. Top은 관계 연산자가 아니므로 다시 쓰기를 수행하면 클러스터형 인덱스 스캔으로 변환되지 않습니다. 옵티마이 저가 상위 운영자를 재배치 할 수있는 경우 비용 추정이 작동하는 방식 때문에 검색 + 조회보다 스캔을 선호합니다.

스캔 및 탐색 비용

매우 높은 수준에서 스캔 및 검색에 대한 옵티마이 저의 비용 모델은 매우 간단합니다. 320 개의 랜덤 검색이 스캔에서 1350 페이지를 읽는 것과 동일한 비용을 추정합니다 . 이것은 현대의 특정 I / O 시스템의 하드웨어 기능과 거의 유사하지는 않지만 실용적인 모델로서 합리적으로 잘 작동합니다.

이 모델은 또한 여러 가지 단순화 된 가정을합니다. 가장 중요한 것은 모든 쿼리가 이미 캐시에 데이터 나 인덱스 페이지없이 시작되는 것으로 가정한다는 것입니다. 이는 모든 I / O가 물리적 I / O를 초래한다는 사실을 의미하지만 실제로는 거의 해당되지 않습니다. 콜드 캐시 (cold cache)를 사용하더라도 프리 페치 및 미리 읽기는 쿼리 프로세서가 필요할 때 실제로 필요한 페이지가 메모리에있을 가능성이 높다는 것을 의미합니다.

메모리에없는 행에 대한 첫 번째 요청으로 인해 전체 페이지가 디스크에서 페치됩니다. 동일한 페이지에서 행에 대한 후속 요청은 실제 I / O를 발생시키지 않을 가능성이 높습니다. 원가 계산 모델에는 이와 같은 효과를 고려하는 논리가 포함되어 있지만 완벽하지는 않습니다.

이러한 모든 것 (및 그 이상)은 옵티마이 저가 예상보다 빨리 스캔으로 전환하는 경향이 있음을 의미합니다. 실제 작업 결과 인 경우 임의 I / O는 '순차적'I / O보다 '훨씬 비쌉니다'-메모리의 페이지에 액세스하는 것은 실제로 매우 빠릅니다. 물리적 읽기가 필요한 경우에도 스캔으로 인해 조각화로 인해 순차 읽기가 발생하지 않을 수 있으며 패턴이 본질적으로 순차적이되도록 탐색이 배치 될 수 있습니다. 또한 최신 I / O 시스템 (특히 솔리드 스테이트)의 성능 변화 특성과 모든 것이 매우 흔들 리기 시작합니다.

행 목표

계획에 최고 운영자가 있으면 원가 계산 방식이 수정됩니다. 옵티마이 저는 스캔을 사용하여 1000 개의 행을 찾는 것이 전체 클러스터 된 인덱스를 스캔 할 필요가 없다는 것을 알기에 충분히 똑똑합니다. 1000 개의 행이 발견되는 즉시 중지 될 수 있습니다. Top 연산자에서 1000 행의 '행 목표'를 설정하고 통계 정보를 사용하여 행 소스에서 필요한 행 수를 추정하기 위해 통계 정보를 사용합니다 (이 경우 스캔). 이 계산에 대한 자세한 내용은 여기에 썼습니다 .

이 답변의 이미지는 SQL Sentry Plan Explorer를 사용하여 작성되었습니다 .

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