요약
주요 문제는 다음과 같습니다.
- 옵티마이 저의 계획 선택은 균일 한 값 분포를 가정합니다.
- 적합한 색인이 없다는 것은 다음을 의미합니다.
- 테이블을 스캔하는 것이 유일한 옵션입니다.
- 조인은 인덱스 중첩 루프 조인이 아니라 순진 중첩 루프 조인입니다. 순진한 조인에서 조인 술어는 조인의 내부를 아래로 밀지 않고 조인에서 평가됩니다.
세부
두 계획은 기본적으로 매우 유사하지만 성능은 매우 다를 수 있습니다.
추가 열로 계획
합리적인 시간 내에 완료되지 않은 여분의 열이있는 것을 먼저 복용하십시오.
흥미로운 기능은 다음과 같습니다.
- 노드 0의 맨 위는 리턴되는 행을 100으로 제한합니다. 또한 옵티 마이저에 대한 행 목표를 설정하므로 계획에서 아래의 모든 항목이 처음 100 개의 행을 빠르게 리턴하도록 선택됩니다.
- 노드 4에서 스캔은
Start_Time
널이 아닌 State
3 또는 4 인 테이블에서 행을 찾고 Operation_Type
나열된 값 중 하나입니다. 테이블은 한 번 완전히 스캔되며 각 행은 언급 된 술어에 대해 테스트됩니다. 모든 테스트를 통과 한 행만 정렬로 진행됩니다. 옵티마이 저는 38,283 개의 행이 적합하다고 추정합니다.
- 노드 3에서 정렬은 노드 4에서 스캔의 모든 행을 사용하고 순서대로 정렬합니다
Start_Time DESC
. 쿼리에서 요청한 최종 프레젠테이션 순서입니다.
- 옵티마이 저는 전체 계획이 100 개의 행을 리턴하기 위해 정렬에서 93 개의 행 (실제로 93.2791)을 읽어야한다고 추정합니다 (조인의 예상 효과를 고려하여).
- 노드 2의 중첩 루프 조인은 내부 입력 (하단 분기)을 94 번 (실제로 94.2791) 실행해야합니다. 기술적 인 이유로 노드 1에서 병렬 처리 중지 교환에 추가 행이 필요합니다.
- 노드 5에서 스캔은 각 반복에서 테이블을 완전히 스캔합니다.
Start_Time
널이 아니고 State
3 또는 4 인 행을 찾습니다 . 이는 각 반복마다 400,875 개의 행을 생성하는 것으로 추정됩니다. 94.2791 회 이상 반복하면 총 행 수는 거의 3 천 8 백만입니다.
- 노드 2의 중첩 루프 조인도 조인 술어를 적용합니다. 또한 그 검사
Operation_Type
(가) 있는지, 일치하는 Start_Time
노드 (4)는 이하보다 Start_Time
가, 노드 (5)로부터 Start_Time
노드 (5)는 이하보다 Finish_Time
노드 (4)에서, 두 것으로 Id
값이 일치하지 않는다.
- 노드 1의 스트림 수집 (병렬 교환 중지)은 100 개의 행이 생성 될 때까지 각 스레드에서 정렬 된 스트림을 병합합니다. 여러 스트림에 걸친 병합의 순서 유지 특성은 5 단계에서 언급 한 추가 행이 필요합니다.
큰 비 효율성은 위의 6 단계와 7 단계에 있습니다. 각 반복에 대해 노드 5에서 테이블을 완전히 스캔하는 것은 옵티마이 저가 예측 한대로 94 번만 발생하는 경우에도 약간 합리적입니다. 노드 2에서 행당 ~ 38 백만 행의 비교 세트도 큰 비용입니다.
결정적으로, 93/94 행 행 목표 추정은 값의 분포에 의존하기 때문에 잘못되었을 가능성이 큽니다. 옵티마이 저는 더 자세한 정보가없는 경우 균일 한 분포를 가정합니다. 간단히 말하면, 이는 테이블의 행 중 1 %가 규정 될 것으로 예상되는 경우 옵티마이 저가 일치하는 1 개의 행을 찾으려면 100 개의 행을 읽어야한다는 것을 의미합니다.
이 쿼리를 완료하는 데 시간이 오래 걸리는 경우, 최종적으로 100 개의 행을 생성하기 위해 93/94 개 이상의 행을 정렬에서 읽어야 할 것입니다. 최악의 경우 정렬의 마지막 행을 사용하여 100 번째 행을 찾습니다. 노드 4에서 옵티마이 저의 추정값이 올바르다 고 가정하면 이는 노드 5에서 38,284 회 스캔을 수행하여 총 150 억 행 과 같은 것을 의미 합니다. 스캔 추정값도 해제되어 있으면 더 좋을 수 있습니다.
이 실행 계획에는 누락 된 인덱스 경고도 포함되어 있습니다.
/*
The Query Processor estimates that implementing the following index
could improve the query cost by 72.7096%.
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, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([Operation_Type],[State],[Start_Time])
INCLUDE ([Id],[Parameters])
옵티마이 저가 테이블에 인덱스를 추가하면 성능이 향상된다는 사실을 경고합니다.
추가 열없이 계획
이는 노드 6에 인덱스 스풀을 추가하고 노드 5에 필터를 추가 한 이전 계획과 본질적으로 정확히 동일한 계획입니다. 중요한 차이점은 다음과 같습니다.
- 노드 6의 인덱스 스풀은 열망 스풀입니다. 그것은 열심히 아래에있는 검사의 결과를 소모하고,에 키가 임시 인덱스를 구축
Operation_Type
하고 Start_Time
와, Id
키가 아닌 열로.
- 노드 2의 중첩 루프 조인은 이제 인덱스 조인입니다. 아니 조인 조건, 여기 대신 당 반복 전류 값 평가
Operation_Type
, Start_Time
, Finish_Time
및 Id
노드 (4)에서의 검사에서 외부 참조로 내측 지점에 전달한다.
- 노드 7의 스캔은 한 번만 수행됩니다.
- 노드 6에서 인덱스 스풀 임시의 행 인덱스를 구하고
Operation_Type
, 현재 외부 기준 값과 일치하고,은 Start_Time
에 의해 정의 된 범위에 Start_Time
및 Finish_Time
외부 참조.
- 노드 5의 필터
Id
는 현재 외부 참조 값인에 대해 인덱스 스풀의 값이 부등식인지 테스트 합니다 Id
.
주요 개선 사항은 다음과 같습니다.
- 내부 스캔은 한 번만 수행됩니다
- 포함 된 열이있는 (
Operation_Type
, Start_Time
) 의 임시 인덱스 Id
는 인덱스 중첩 루프 조인을 허용합니다. 인덱스는 매번 전체 테이블을 스캔하지 않고 각 반복에서 일치하는 행을 찾는 데 사용됩니다.
이전과 마찬가지로 옵티 마이저에는 누락 된 인덱스에 대한 경고가 포함되어 있습니다.
/*
The Query Processor estimates that implementing the following index
could improve the query cost by 24.1475%.
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, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([State],[Start_Time])
INCLUDE ([Id],[Operation_Type])
GO
결론
옵티마이 저가 임시 인덱스를 작성하도록 선택했기 때문에 추가 컬럼이없는 계획이 더 빠릅니다.
여분의 열이있는 계획은 임시 색인을 작성하는 데 더 많은 비용이들 것입니다. [Parameters
] 열은 nvarchar(2000)
인덱스의 각 행에 4000 바이트를 추가 할 것이다. 추가 비용은 옵티마이 저가 각 실행에 대해 임시 인덱스를 작성하면 비용을 지불하지 않는다고 확신시키기에 충분합니다.
옵티마이 저는 두 경우 모두 영구 색인이 더 나은 솔루션이라고 경고합니다. 인덱스의 이상적인 구성은 더 넓은 워크로드에 따라 다릅니다. 이 특정 쿼리의 경우 제안 된 인덱스는 합리적인 시작점이지만 관련된 이점과 비용을 이해해야합니다.
추천
광범위한 쿼리가이 쿼리에 도움이됩니다. 중요한 점은 일종의 비 클러스터형 인덱스가 필요하다는 것입니다. 제공된 정보에서 내 의견으로는 합리적인 색인은 다음과 같습니다.
CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time);
또한 쿼리를 조금 더 잘 구성하고 [Parameters]
상위 100 행이 발견 될 때까지 ( Id
키로 사용하여) 클러스터형 인덱스에서 넓은 열을 찾는 것을 지연시키고 싶습니다 .
SELECT TOP (100)
BTQ1.id,
BTQ2.id,
BTQ3.[Parameters],
BTQ4.[Parameters]
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
ON BTQ2.Operation_Type = BTQ1.Operation_Type
AND BTQ2.Start_Time > BTQ1.Start_Time
AND BTQ2.Start_Time < BTQ1.Finish_Time
AND BTQ2.id != BTQ1.id
-- Look up the [Parameters] values
JOIN dbo.Batch_Tasks_Queue AS BTQ3
ON BTQ3.Id = BTQ1.Id
JOIN dbo.Batch_Tasks_Queue AS BTQ4
ON BTQ4.Id = BTQ2.Id
WHERE
BTQ1.[State] IN (3, 4)
AND BTQ2.[State] IN (3, 4)
AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
-- These predicates are not strictly needed
AND BTQ1.Start_Time IS NOT NULL
AND BTQ2.Start_Time IS NOT NULL
ORDER BY
BTQ1.Start_Time DESC;
를 Where [Parameters]
열이 필요하지 않은 쿼리를 단순화 할 수 있습니다 :
SELECT TOP (100)
BTQ1.id,
BTQ2.id
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
ON BTQ2.Operation_Type = BTQ1.Operation_Type
AND BTQ2.Start_Time > BTQ1.Start_Time
AND BTQ2.Start_Time < BTQ1.Finish_Time
AND BTQ2.id != BTQ1.id
WHERE
BTQ1.[State] IN (3, 4)
AND BTQ2.[State] IN (3, 4)
AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ1.Start_Time IS NOT NULL
AND BTQ2.Start_Time IS NOT NULL
ORDER BY
BTQ1.Start_Time DESC;
FORCESEEK
힌트는 최적화 프로그램이 인덱스 중첩 루프 계획 선택 보장에있다 (물론 이러한 유형의 작업을하지 경향이 다른 해시 또는 (많은 많은) 병합 조인 선택하는 최적화를위한 비용 기반의 유혹,가 실제로 쿼리는 둘 다 큰 잔차, 해시의 경우 버킷 당 많은 항목 및 병합을위한 많은 되감기로 끝납니다.
대안
쿼리 (구체적인 값 포함)가 읽기 성능에 특히 중요한 경우 필터링 된 두 인덱스를 대신 고려합니다.
CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time)
WHERE
Start_Time IS NOT NULL
AND [State] IN (3, 4)
AND Operation_Type <> 23
AND Operation_Type <> 24
AND Operation_Type <> 25
AND Operation_Type <> 26
AND Operation_Type <> 27
AND Operation_Type <> 28
AND Operation_Type <> 30;
CREATE NONCLUSTERED INDEX i2
ON dbo.Batch_Tasks_Queue (Operation_Type, [State], Start_Time)
WHERE
Start_Time IS NOT NULL
AND [State] IN (3, 4)
AND Operation_Type <> 23
AND Operation_Type <> 24
AND Operation_Type <> 25
AND Operation_Type <> 26
AND Operation_Type <> 27
AND Operation_Type <> 28
AND Operation_Type <> 30;
[Parameters]
열이 필요없는 쿼리의 경우 필터링 된 인덱스를 사용한 예상 계획은 다음과 같습니다.
인덱스 스캔은 추가 술어를 평가하지 않고 모든 규정 행을 자동으로 리턴합니다. 인덱스 중첩 루프 조인의 각 반복에 대해 인덱스 탐색은 두 가지 탐색 조작을 수행합니다.
- 탐색 접두어가 on
Operation_Type
및 State
= 3 인 다음 Start_Time
값 의 범위를 찾고 Id
부등식의 잔존 술어 를 찾습니다 .
- 탐색 접두어가 on
Operation_Type
및 State
= 4이면 Start_Time
값 의 범위 , Id
부등식의 잔존 조건 을 찾습니다 .
를 Where [Parameters]
열이 필요하다, 쿼리 계획은 단순히 각 테이블 100 싱글 조회의 최대를 추가합니다 :
마지막으로 numeric
, 적용 가능한 경우 대신 내장 표준 정수 유형을 사용하는 것이 좋습니다 .