날짜 비교로 하위 쿼리 수행 불량


15

하위 쿼리를 사용하여 일치하는 필드가있는 모든 이전 레코드의 총 개수를 찾는 경우 성능이 50k 레코드만큼 적은 테이블에서 끔찍합니다. 하위 쿼리가 없으면 쿼리는 몇 밀리 초 안에 실행됩니다. 하위 쿼리를 사용하면 실행 시간이 1 분 이상입니다.

이 쿼리의 결과는 다음과 같아야합니다.

  • 지정된 기간 내에 해당 레코드 만 포함하십시오.
  • 날짜 범위에 관계없이 현재 레코드를 포함하지 않는 모든 이전 레코드 수를 포함합니다.

기본 테이블 스키마

Activity
======================
Id int Identifier
Address varchar(25)
ActionDate datetime2
Process varchar(50)
-- 7 other columns

데이터 예

Id  Address     ActionDate (Time part excluded for simplicity)
===========================
99  000         2017-05-30
98  111         2017-05-30
97  000         2017-05-29
96  000         2017-05-28
95  111         2017-05-19
94  222         2017-05-30

예상 결과

의 기간에 2017-05-29대해2017-05-30

Id  Address     ActionDate    PriorCount
=========================================
99  000         2017-05-30    2  (3 total, 2 prior to ActionDate)
98  111         2017-05-30    1  (2 total, 1 prior to ActionDate)
94  222         2017-05-30    0  (1 total, 0 prior to ActionDate)
97  000         2017-05-29    1  (3 total, 1 prior to ActionDate)

레코드 96 및 95는 결과에서 제외되지만 PriorCount하위 쿼리에 포함됩니다.

현재 검색어

select 
    *.a
    , ( select count(*) 
        from Activity
        where 
            Activity.Address = a.Address
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc

현재 색인

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON [dbo].[Activity]
(
    [ActionDate] ASC
)
INCLUDE ([Address]) WITH (
    PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF, 
    DROP_EXISTING = OFF, 
    ONLINE = OFF, 
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON
)

질문

  • 이 쿼리의 성능을 향상시키기 위해 어떤 전략을 사용할 수 있습니까?

편집 1
DB에서 수정할 수있는 질문에 대한 답으로 : 테이블 구조가 아니라 인덱스를 수정할 수 있습니다.

편집 2
이제 Address열에 기본 색인을 추가 했지만 그다지 향상되지는 않았습니다. 현재 임시 테이블을 만들고 값없이 값을 삽입 PriorCount한 다음 각 행을 특정 카운트로 업데이트 하여 성능이 훨씬 뛰어납니다 .

편집 3
인덱스 스풀 Joe Obbish (허용 된 답변)에서 문제가 발견되었습니다. new를 추가 nonclustered index [xyz] on [Activity] (Address) include (ActionDate)하면 임시 테이블을 사용하지 않고 쿼리 시간이 1 분에서 1 초 미만으로 줄었습니다 (편집 2 참조).

답변:


17

에 대한 인덱스 정의를 사용하면 IDX_my_nmeSQL Server는 ActionDate열이 아닌 열 을 사용하여 검색 할 수 있습니다 Address. 인덱스에는 하위 쿼리를 처리하는 데 필요한 모든 열이 포함되어 있지만 해당 하위 쿼리에 대해서는 선택 사항이 아닐 수 있습니다. 테이블의 거의 모든 데이터의 ActionDate값이보다 이전 이라고 가정하십시오 '2017-05-30'. seek는 ActionDate < '2017-05-30'인덱스에서 거의 모든 행을 반환하며, 인덱스에서 행을 가져온 후 추가로 필터링됩니다. 쿼리가 200 개의 행을 반환하면에 대해 거의 200 개의 전체 인덱스 스캔을 수행하는 것입니다 IDX_my_nme. 이는 인덱스에서 약 50000 * 200 = 1 천만 개의 행을 읽음을 의미합니다.

Address쿼리에 대한 전체 통계 정보를 제공하지 않았기 때문에 하위 쿼리에 대한 탐색 이 훨씬 선택적 일 수 있습니다. 그러나 인덱스 만 생성하고 Address테이블에 고유 한 값이 10k 라고 가정합니다 Address. 새 인덱스를 사용하면 SQL Server는 하위 쿼리를 실행할 때마다 인덱스에서 5 개의 행만 검색하면되므로 인덱스에서 약 200 * 5 = 1000 개의 행을 읽을 수 있습니다.

SQL Server 2016에 대해 테스트 중이므로 약간의 구문 차이가있을 수 있습니다. 아래는 데이터 배포에 대해 위와 유사한 가정을 한 샘플 데이터입니다.

CREATE TABLE #Activity (
    Id int NOT NULL,
    [Address] varchar(25) NULL,
    ActionDate datetime2 NULL,
    FILLER varchar(100),
    PRIMARY KEY (Id)
);

INSERT INTO #Activity WITH (TABLOCK)
SELECT TOP (50000) -- 50k total rows
x.RN
, x.RN % 10000 -- 10k unique addresses
, DATEADD(DAY, x.RN / 100, '20160201') -- 100 rows per day
, REPLICATE('Z', 100)
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) x;

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([ActionDate] ASC) INCLUDE ([Address]);

질문에 설명 된대로 색인을 만들었습니다. 질문의 데이터와 동일한 데이터를 반환하는이 쿼리에 대해 테스트하고 있습니다.

select 
    a.*
    , ( select count(*) 
        from #Activity Activity
        where 
            Activity.[Address] = a.[Address]
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from #Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc;

인덱스 스풀을 얻습니다. 기본 수준에서 의미하는 것은 테이블에 대한 기존 인덱스가 없기 때문에 쿼리 최적화 프로그램이 즉석에서 임시 인덱스를 작성한다는 것입니다.

인덱스 스풀

쿼리가 여전히 빨리 완료됩니다. 시스템에서 인덱스 스풀 최적화를 얻지 못했거나 테이블 정의 또는 쿼리와 다른 점이있을 수 있습니다. 교육 목적으로 문서화되지 않은 기능 OPTION (QUERYRULEOFF BuildSpool)을 사용하여 인덱스 스풀을 비활성화 할 수 있습니다 . 계획은 다음과 같습니다.

잘못된 인덱스 탐색

간단한 인덱스 검색의 출현으로 속지 마십시오. SQL Server는 인덱스에서 거의 천만 개의 행을 읽습니다.

인덱스에서 10M 행

쿼리를 두 번 이상 실행하려면 쿼리 최적화 프로그램이 실행될 때마다 인덱스를 만드는 것이 의미가 없습니다. 이 쿼리에 더 선택적인 인덱스를 미리 만들 수 있습니다.

CREATE NONCLUSTERED INDEX [IDX_my_nme_2] ON #Activity
([Address] ASC) INCLUDE (ActionDate);

계획은 이전과 비슷합니다.

인덱스 탐색

그러나 새 인덱스를 사용하면 SQL Server는 인덱스에서 1000 개의 행만 읽습니다. 800 개의 행이 계산되도록 반환됩니다. 인덱스는보다 선택적으로 정의 할 수 있지만 데이터 분포에 따라 충분할 수 있습니다.

좋은 탐색

테이블에서 추가 인덱스를 정의 할 수 없으면 창 함수 사용을 고려할 것입니다. 다음과 같이 작동합니다.

SELECT t.*
FROM
(
    select 
        a.*
        , -1 + ROW_NUMBER() OVER (PARTITION BY [Address] ORDER BY ActionDate) PriorCount
    from #Activity a
) t
where t.ActionDate between '2017-05-29' and '2017-05-30'
order by t.ActionDate desc;

이 쿼리는 데이터를 한 번 스캔하지만 값 비싼 정렬을 수행하고 ROW_NUMBER()테이블의 모든 행에 대한 함수를 계산 하므로 여기에 추가 작업이 필요한 것 같습니다.

나쁜 종류

그러나 해당 코드 패턴을 정말로 좋아한다면 색인을 정의하여 더 효율적으로 만들 수 있습니다.

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([Address], [ActionDate]) INCLUDE (FILLER);

이렇게하면 정렬이 끝쪽으로 이동하여 훨씬 저렴합니다.

좋은 종류

이 중 어느 것도 도움이되지 않으면 실제 실행 계획을 포함하여 질문에 더 많은 정보를 추가해야합니다.


1
찾은 인덱스 스풀이 문제였습니다. 새로운을 추가 nonclustered index [xyz] on [Activity] (Address) include (ActionDate)하면 쿼리 시간이 1 분에서 1 초 미만으로 줄어 들었습니다. 내가 할 수 있다면 +10 감사!
메트로 스머프
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.