두 개의 열을 추가 할 때 쿼리가 응답하지 않습니다


9

내 선택에 두 개의 열을 추가하면 쿼리가 응답하지 않습니다. 열 유형은입니다 nvarchar(2000). 조금 이례적입니다.

  • SQL Server 버전은 2014입니다.
  • 기본 인덱스는 하나만 있습니다.
  • 전체 레코드는 1000 행입니다.

실행 계획은 다음과 같습니다 ( XML showplan ).

여기에 이미지 설명을 입력하십시오

이후 실행 계획 ( XML 실행 계획 ) :

여기에 이미지 설명을 입력하십시오

다음은 쿼리입니다.

select top(100)
  Batch_Tasks_Queue.id,
  btq.id,
  Batch_Tasks_Queue.[Parameters], -- this field
  btq.[Parameters]  -- and this field
from
        Batch_Tasks_Queue with(nolock)
    inner join  Batch_Tasks_Queue btq with(nolock)  on  Batch_Tasks_Queue.Start_Time < btq.Start_Time
                            and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
                            and Batch_Tasks_Queue.id <> btq.id                            
                            and btq.Start_Time is not null
                            and btq.State in (3, 4)                          
where
    Batch_Tasks_Queue.Start_Time is not null      
    and Batch_Tasks_Queue.State in (3, 4)
    and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
    and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)

order by
    Batch_Tasks_Queue.Start_Time desc

전체 결과 수는 17 행입니다. 더티 데이터 (노락 힌트)는 중요하지 않습니다.

다음은 테이블 구조입니다.

CREATE TABLE [dbo].[Batch_Tasks_Queue](
    [Id] [int] NOT NULL,
    [OBJ_VERSION] [numeric](8, 0) NOT NULL,
    [Operation_Type] [numeric](2, 0) NULL,
    [Request_Time] [datetime] NOT NULL,
    [Description] [varchar](1000) NULL,
    [State] [numeric](1, 0) NOT NULL,
    [Start_Time] [datetime] NULL,
    [Finish_Time] [datetime] NULL,
    [Parameters] [nvarchar](2000) NULL,
    [Response] [nvarchar](max) NULL,
    [Billing_UserId] [int] NOT NULL,
    [Planned_Start_Time] [datetime] NULL,
    [Input_FileId] [uniqueidentifier] NULL,
    [Output_FileId] [uniqueidentifier] NULL,
    [PRIORITY] [numeric](2, 0) NULL,
    [EXECUTE_SEQ] [numeric](2, 0) NULL,
    [View_Access] [numeric](1, 0) NULL,
    [Seeing] [numeric](1, 0) NULL,
 CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO    
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue]  WITH NOCHECK ADD  CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO

이 질문에 대한 토론 이이 대화방 으로 이동 되었습니다 .
Paul White 9

답변:


15

요약

주요 문제는 다음과 같습니다.

  • 옵티마이 저의 계획 선택은 균일 한 값 분포를 가정합니다.
  • 적합한 색인이 없다는 것은 다음을 의미합니다.
    • 테이블을 스캔하는 것이 유일한 옵션입니다.
    • 조인은 인덱스 중첩 루프 조인이 아니라 순진 중첩 루프 조인입니다. 순진한 조인에서 조인 술어는 조인의 내부를 아래로 밀지 않고 조인에서 평가됩니다.

세부

두 계획은 기본적으로 매우 유사하지만 성능은 매우 다를 수 있습니다.

추가 열로 계획

합리적인 시간 내에 완료되지 않은 여분의 열이있는 것을 먼저 복용하십시오.

느린 계획

흥미로운 기능은 다음과 같습니다.

  1. 노드 0의 맨 위는 리턴되는 행을 100으로 제한합니다. 또한 옵티 마이저에 대한 행 목표를 설정하므로 계획에서 아래의 모든 항목이 처음 100 개의 행을 빠르게 리턴하도록 선택됩니다.
  2. 노드 4에서 스캔은 Start_Time널이 아닌 State3 또는 4 인 테이블에서 행을 찾고 Operation_Type나열된 값 중 하나입니다. 테이블은 한 번 완전히 스캔되며 각 행은 언급 된 술어에 대해 테스트됩니다. 모든 테스트를 통과 한 행만 정렬로 진행됩니다. 옵티마이 저는 38,283 개의 행이 적합하다고 추정합니다.
  3. 노드 3에서 정렬은 노드 4에서 스캔의 모든 행을 사용하고 순서대로 정렬합니다 Start_Time DESC. 쿼리에서 요청한 최종 프레젠테이션 순서입니다.
  4. 옵티마이 저는 전체 계획이 100 개의 행을 리턴하기 위해 정렬에서 93 개의 행 (실제로 93.2791)을 읽어야한다고 추정합니다 (조인의 예상 효과를 고려하여).
  5. 노드 2의 중첩 루프 조인은 내부 입력 (하단 분기)을 94 번 (실제로 94.2791) 실행해야합니다. 기술적 인 이유로 노드 1에서 병렬 처리 중지 교환에 추가 행이 필요합니다.
  6. 노드 5에서 스캔은 각 반복에서 테이블을 완전히 스캔합니다. Start_Time널이 아니고 State3 또는 4 인 행을 찾습니다 . 이는 각 반복마다 400,875 개의 행을 생성하는 것으로 추정됩니다. 94.2791 회 이상 반복하면 총 행 수는 거의 3 천 8 백만입니다.
  7. 노드 2의 중첩 루프 조인도 조인 술어를 적용합니다. 또한 그 검사 Operation_Type(가) 있는지, 일치하는 Start_Time노드 (4)는 이하보다 Start_Time가, 노드 (5)로부터 Start_Time노드 (5)는 이하보다 Finish_Time노드 (4)에서, 두 것으로 Id값이 일치하지 않는다.
  8. 노드 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에 필터를 추가 한 이전 계획과 본질적으로 정확히 동일한 계획입니다. 중요한 차이점은 다음과 같습니다.

  1. 노드 6의 인덱스 스풀은 열망 스풀입니다. 그것은 열심히 아래에있는 검사의 결과를 소모하고,에 키가 임시 인덱스를 구축 Operation_Type하고 Start_Time와, Id키가 아닌 열로.
  2. 노드 2의 중첩 루프 조인은 이제 인덱스 조인입니다. 아니 조인 조건, 여기 대신 당 반복 전류 값 평가 Operation_Type, Start_Time, Finish_TimeId노드 (4)에서의 검사에서 외부 참조로 내측 지점에 전달한다.
  3. 노드 7의 스캔은 한 번만 수행됩니다.
  4. 노드 6에서 인덱스 스풀 임시의 행 인덱스를 구하고 Operation_Type, 현재 외부 기준 값과 일치하고,은 Start_Time에 의해 정의 된 범위에 Start_TimeFinish_Time외부 참조.
  5. 노드 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]열이 필요없는 쿼리의 경우 필터링 된 인덱스를 사용한 예상 계획은 다음과 같습니다.

간단한 필터링 된 인덱스 계획

인덱스 스캔은 추가 술어를 평가하지 않고 모든 규정 행을 자동으로 리턴합니다. 인덱스 중첩 루프 조인의 각 반복에 대해 인덱스 탐색은 두 가지 탐색 조작을 수행합니다.

  1. 탐색 접두어가 on Operation_TypeState= 3 인 다음 Start_Time값 의 범위를 찾고 Id부등식의 잔존 술어 를 찾습니다 .
  2. 탐색 접두어가 on Operation_TypeState= 4이면 Start_Time값 의 범위 , Id부등식의 잔존 조건 을 찾습니다 .

를 Where [Parameters]열이 필요하다, 쿼리 계획은 단순히 각 테이블 100 싱글 조회의 최대를 추가합니다 :

추가 열이있는 필터링 된 인덱스 계획

마지막으로 numeric, 적용 가능한 경우 대신 내장 표준 정수 유형을 사용하는 것이 좋습니다 .


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