큰 테이블에서 조인 최적화


10

~ 2 억 5 천만 레코드로 테이블에 액세스하는 쿼리에서 더 많은 성능을 동축하려고합니다. 실제 (추정되지 않은) 실행 계획을 읽은 첫 번째 병목 현상은 다음과 같은 쿼리입니다.

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
where
    a.added between @start and @end;

관련된 테이블 및 인덱스의 정의는 아래를 참조하십시오.

실행 계획은 중첩 루프가 #smalltable에서 사용되고 있고 hugetable에 대한 인덱스 스캔이 480 번 (#smalltable의 각 행에 대해) 실행되고 있음을 나타냅니다. 이것은 거꾸로 보이므로 병합 조인을 대신 사용하려고했습니다.

select
    b.stuff,
    a.added,
    a.value
from
    dbo.hugetable a with(index = ix_hugetable)
    inner merge join
    #smalltable b with(index(1)) on a.fk = b.pk
where
    a.added between @start and @end;

문제의 인덱스 (아래의 전체 정의는 참조)는 열 fk (결합 조인), 추가 (where 절에서 사용됨) 및 id (무효)를 오름차순으로 포함하며 value를 포함 합니다 .

그러나이 작업을 수행하면 쿼리가 2 1/2 분에서 9 이상으로 폭발합니다. 힌트를 사용하면 각 테이블을 한 번만 통과하는보다 효율적인 조인이 더 효율적으로 이루어 지길 바랍니다.

모든 안내를 환영합니다. 필요한 경우 추가 정보가 제공됩니다.

업데이트 (2011/06/02)

테이블에서 인덱싱을 재구성 한 후 성능이 크게 향상되었지만 거대한 테이블의 데이터를 요약 할 때 새로운 장애물에 부딪 쳤습니다. 결과는 월별 요약이며 현재 다음과 같습니다.

select
    b.stuff,
    datediff(month, 0, a.added),
    count(a.value),
    sum(case when a.value > 0 else 1 end) -- this triples the running time!
from
    dbo.hugetable a
    inner join
    #smalltable b on a.fk = b.pk
group by
    b.stuff,
    datediff(month, 0, a.added);

현재 hugetable 에는 클러스터형 인덱스 pk_hugetable (added, fk)(기본 키)와 클러스터되지 않은 인덱스가 ix_hugetable (fk, added)있습니다.

위의 네 번째 열이 없으면 옵티마이 저는 이전과 같이 중첩 입력 조인을 사용합니다. #smalltable을 외부 입력으로 사용하고 비 클러스터형 인덱스 검색을 내부 루프로 사용합니다 (다시 480 번 실행). 나에게 중요한 것은 예상 행 (12,958.4)과 실제 행 (74,668,468) 사이의 불일치입니다. 이러한 탐색의 상대 비용은 45 %입니다. 그러나 실행 시간은 1 분 미만입니다.

네 번째 열에서는 실행 시간이 4 분으로 급증합니다. 이번에는 동일한 상대 비용 (45 %)으로 클러스터 된 인덱스를 검색 (2 회 실행)하고 해시 일치 (30 %)를 통해 집계 한 다음 #smalltable (0 %)에서 해시 조인을 수행합니다.

다음 행동에 대해서는 확신이 없습니다. 내 관심사는 날짜 범위 검색이나 조인 술어가 보장되지 않거나 결과 집합을 크게 줄일 가능성이 없다는 것입니다. 대부분의 경우 날짜 범위는 레코드의 10-15 % 만 다듬고 fk 의 내부 조인은 20-30 %를 필터링 할 수 있습니다.


Will A가 요청한 결과는 sp_spaceused다음 과 같습니다.

name      | rows      | reserved    | data        | index_size  | unused
hugetable | 261774373 | 93552920 KB | 18373816 KB | 75167432 KB | 11672 KB

#smalltable 은 다음과 같이 정의됩니다.

create table #endpoints (
    pk uniqueidentifier primary key clustered,
    stuff varchar(6) null
);

하지만 dbo.hugetable는 다음과 같이 정의된다

create table dbo.hugetable (
    id uniqueidentifier not null,
    fk uniqueidentifier not null,
    added datetime not null,
    value decimal(13, 3) not null,

    constraint pk_hugetable primary key clustered (
        fk asc,
        added asc,
        id asc
    )
    with (
        pad_index = off, statistics_norecompute = off,
        ignore_dup_key = off, allow_row_locks = on,
        allow_page_locks = on
    )
    on [primary]
)
on [primary];

다음 색인이 정의되어 있습니다.

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc, id asc
) include(value) with (
    pad_index = off, statistics_norecompute = off,
    sort_in_tempdb = off, ignore_dup_key = off,
    drop_existing = off, online = off,
    allow_row_locks = on, allow_page_locks = on
)
on [primary];

아이디 필드 주장 이전 DBA에서 인공물 중복 모든 테이블이 사방 GUID, 예외없이이 있어야합니다.


sp_spaceused 'dbo.hugetable'의 결과를 포함시킬 수 있습니까?
Will A

테이블 정의 시작 바로 위에 추가되었습니다.
빠른 조 스미스

그것은 확실하다. 그것의 우스운 크기는 내가 이것을 조사하는 이유입니다.
빠른 조 스미스

답변:


5

당신의 ix_hugetable외모 꽤 쓸모없는 이유는

  • 그것은 클러스터 된 인덱스 (PK)
  • 클러스터 된 인덱스는 키가 아닌 모든 열을 포함하기 때문에 차이가 없습니다.

또한 :-added 또는 fk가 첫 번째 여야 함-ID가 첫 번째 = 많이 사용되지 않음

클러스터 된 키를로 변경 (added, fk, id)하고 삭제 해보십시오 ix_hugetable. 이미 시도했습니다 (fk, added, id). 다른 것이 없으면 많은 디스크 공간과 인덱스 유지 관리 비용을 절약 할 수 있습니다

다른 옵션은 테이블 순서와 함께 FORCE ORDER 힌트를 시도하고 JOIN / INDEX 힌트를 사용하지 않는 것입니다. 옵티 마이저 옵션을 제거하기 때문에 JOIN / INDEX 힌트를 개인적으로 사용하지 않습니다. 몇 년 전에 나는 (SQL 전문가가있는 세미나) FORCE ORDER 힌트가 큰 테이블을 가질 때 도움이 될 수 있다고 들었습니다.

아, 그리고 DBA가 사는 곳을 알려 주면 타악기 조정을 준비 할 수 있습니다

6 월 02 일 업데이트 후 편집

네 번째 열은 비 클러스터형 인덱스의 일부가 아니므로 클러스터형 인덱스를 사용합니다.

값 인덱스를 포함하도록 NC 인덱스를 변경하여 클러스터형 인덱스의 값 열에 액세스 할 필요가 없도록하십시오.

create nonclustered index ix_hugetable on dbo.hugetable (
    fk asc, added asc
) include(value)

참고 : 값이 널 입력 가능하지 않은 경우 COUNT(*)의미 적으로 동일합니다 . 그러나 SUM의 경우 존재 하지 않고 실제 가치 가 필요합니다 .

변경할 경우 예를 들어, COUNT(value)COUNT(DISTINCT value) 하지 않고 인덱스를 변경이 값으로 값을 처리하기 때문에이없는 존재로, 다시 쿼리를 중단해야한다.

쿼리에는 3 개의 열 (added, fk, value)이 필요합니다. 처음 2 개는 필터링 / 결합되어 주요 열입니다. 값은 그대로 사용되므로 포함 할 수 있습니다. 취재 지수의 고전적인 사용.


Hah, 클러스터링 및 비 클러스터형 인덱스가 fk & 다른 순서로 추가되었다는 사실을 머릿속에 들었습니다. 나는 그것이 처음부터 이런 식으로 설정되었다는 것을 믿을 수 없을 정도로 거의 알지 못했습니다. 내일 클러스터형 인덱스를 변경 한 다음 다시 빌드하는 동안 커피를 마시 러 길을갑니다.
빠른 조 스미스

나는 인덱싱을 변경했고 큰 테이블에서 탐색 횟수를 줄이려고 시도했지만 아무 소용이 없습니다. 내 질문이 업데이트되었습니다.
빠른 조 스미스

@Quick Joe Smith : 내 답변 업데이트
gbn

그렇습니다. 인덱스 재 구축에 시간이 오래 걸리기 때문에 잊어 버렸고 처음에는 완전히 관련이없는 일을했다고 생각했습니다.
빠른 조 스미스

2

hugetable에서만 인덱스를 정의하십시오 added.

DB는 왼쪽부터 세는 값이 있으므로 열 목록의 가장 오른쪽에있는 다중 부분 (다중 열) 인덱스를 사용합니다. 쿼리는 fk첫 번째 쿼리의 where 절에 지정되지 않으므로 인덱스를 무시합니다.


실행 계획 프로그램은 인덱스 (ix_hugetable)는 것이 된다 탐색했습니다된다. 아니면이 인덱스가 쿼리에 적합하지 않다는 말입니까?
빠른 조 스미스

색인이 적합하지 않습니다. "인덱스 사용"방법을 아는 사람 경험에 의하면 이것이 당신의 문제입니다. 시도하고 어떻게 진행되는지 알려주십시오.
보헤미안

@Quick Joe Smith-@Bohemian의 제안을 시도 했습니까? 결과는 어디입니까?
Lieven Keersmaekers

2
동의하지 않습니다 : ON 절이 논리적으로 먼저 처리되고 실제로 실제로 WHERE이므로 OP는 두 열을 먼저 시도해야합니다. fk에 대한 인덱싱 없음 = JOIN에 대한 fk 값을 얻기위한 클러스터 된 인덱스 스캔 또는 키 조회. 당신이 묘사 한 행동에 대한 언급을 추가 할 수 있습니까? 특히이 RDBMS에 대한 이전의 대답이 거의없는 SQL Server의 경우. 실제로, -1로 소급하여 -1이 주석을 입력합니다
gbn

2

실행 계획은 중첩 루프가 #smalltable에서 사용되고 있고 hugetable에 대한 인덱스 스캔이 480 번 (#smalltable의 각 행에 대해) 실행되고 있음을 나타냅니다.

이것이 올바른 선택에 루프 조인이 있다고 가정하면 쿼리 최적화 프로그램에서 사용할 것으로 예상되는 순서입니다. 대안은 250M 회 반복하고 매번 #temp 테이블을 조회하는 것입니다. 시간 / 일이 걸릴 수 있습니다.

당신이 강제하고 인덱스는 조인 병합에 사용되는 거의 250M 행 * '각 행의 크기'- 작은되지에서 적어도 GB의 몇. sp_spaceused출력 '2GB'로 판단 하면 상당히 과소 평가 될 수 있습니다. MERGE 조인은 인덱스를 통해 매우 I / O 집약적 인 인덱스를 통과해야합니다.


내 이해는 3 가지 유형의 조인 알고리즘이 있으며 두 가지 입력이 조인 술어에 의해 정렬 될 때 병합 조인이 최상의 성능을 갖는다는 것입니다. 옳고 그름, 이것이 내가 얻는 결과입니다.
빠른 조 스미스

2
그러나 이것보다 더 많은 것이 있습니다. #smalltable에 많은 수의 행이 있으면 병합 조인이 적합 할 수 있습니다. 이름에서 알 수 있듯이 행 수가 적은 경우 루프 조인이 올바른 선택이 될 수 있습니다. #smalltable에 하나 또는 두 개의 행이 있고 다른 테이블의 소수 행과 일치한다고 가정합니다. 여기에서 병합 조인을 정당화하기는 어렵습니다.
Will A

나는 그것에 더 많은 것이 있다고 생각했다. 나는 그것이 무엇인지 몰랐습니다. 아마 당신이 아마 짐작했듯이 데이터베이스 최적화는 내 강력한 선택이 아닙니다.
빠른 조 스미스

@Quick Joe Smith-sp_spaceused에 감사드립니다. 75GB의 인덱스와 18GB의 데이터-ix_hugetable이 테이블의 유일한 인덱스가 아닙니까?
Will A

1
윌 +1 플래너는 현재 올바른 일을하고 있습니다. 문제는 테이블이 클러스터되는 방식으로 인해 무작위 디스크 탐색에 있습니다.
Denis de Bernardy

1

색인이 잘못되었습니다. 인덱스 dos 및 donts를 참조하십시오 .

상황에 따라 유용한 유일한 색인은 작은 테이블의 기본 키에 대한 것입니다. 따라서 유일한 합리적인 계획은 작은 테이블을 스캔하고 큰 테이블과 엉망을 중첩시키는 것입니다.

에 클러스터형 인덱스를 추가해보십시오 hugetable(added, fk). 이를 통해 플래너는 거대한 테이블에서 적용 가능한 행을 찾고 중첩 루프 또는 병합을 작은 테이블과 조인합니다.


그 링크 주셔서 감사합니다. 나는 내일 일할 때 이것을 시도 할 것입니다.
빠른 조 스미스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.