2009-04-16 3 views
11

SQL Server 2005를 사용하여 지속적으로 들어오는 데이터 (초당 5-15 회 업데이트)를 추적합니다. 테이블 중 하나가 외설적 인 쿼리를 수행하기 시작한 지 2 개월 만에 프로덕션을 진행 한 후에 나타났습니다.큰 데이터 집합에 대해 SQL Server의 고유 쿼리가 느림

테이블은 3 열이 있습니다 - 일련 번호 (클러스터)

  • typeUUID -

    • id를 삽입이 발생하기 전에 GUID가 생성; 함께 유형
    • typeName 그룹에 사용 - 우리가 실행하는 쿼리의

    하나는 typeName 필드에서 구분된다 (... 대만족) 유형 이름 :

    SELECT DISTINCT [typeName] FROM [types] WITH (nolock); 
    

    typeName 필드의 클러스터되지 않은 비 고유 오름차순 인덱스가 있습니다. 이 테이블에는 현재 약 200M 개의 레코드가 있습니다. 우리가이 쿼리를 실행할 때 쿼리는 5m 58 초가 걸렸습니다! 아마도 우리는 인덱스가 어떻게 작동하는지 이해하지 못하고 있습니다 ... 그러나 나는 우리가 그들을 잘못 이해했다고 생각하지 않았습니다 많이. 내가 기대하는 것처럼,이 테이블을 스캔하는 것, 약 10 초

    SELECT DISTINCT [typeName] FROM (SELECT TOP 1000000 [typeName] FROM [types] WITH (nolock)) AS [subtbl] 
    

    이 쿼리 반환 :

    이 조금 더 테스트하기 위해, 우리는 다음 쿼리를 실행했습니다.

    여기에 누락 된 것이 있습니까? 첫 번째 쿼리가 왜 그렇게 오래 걸릴까요?

    편집 : 아, 내 사과, 첫 번째 쿼리는 76 레코드를 반환, ninesided 감사합니다.

    후속 조치 : 답장을 보내 주셔서 감사합니다. 지금은 나에게 더 의미가 있습니다. 인덱스가 없으면 인덱스가있는 200M 행의 테이블을 스캔하고 200M 행의 인덱스 스캔을 수행합니다 ...

    SQL Server는 인덱스를 선호하며 약간의 성능 향상을 제공합니다 , 그러나 흥분하는 것을 아무것도. 색인을 다시 작성하면 질의 시간이 6m가 아닌 3m를 약간 넘는 수준으로 향상되었지만 충분하지는 않습니다. 상사에게 테이블 구조를 표준화 할 것을 권유합니다.

    다시 한 번 도움을 주셔서 감사합니다.

  • +0

    일반적으로 몇 가지 고유 한 유형을 예상합니까? – ninesided

    +0

    솔직히 말하면 디자인에 근본적인 결함이있는 것 같습니다. 200M 레코드가 "들어오는"테이블에 있습니까? 그들은 잠시 둘러 본 후 다른 곳으로 밀어 넣을 수 없습니까? 응용 프로그램을 이해하지 않고 더 나은 조언을하는 것은 힘들지만 심각한 리펙토링이 필요할 수도있는 것처럼 들립니다. – kquinn

    +0

    네, 우리는 현재 4 개월 분량의 데이터를 다루고있는 많은 데이터를 보유하고 있습니다. 우리는 데이터를 분할 할 필요가있을 것입니다, 그러나 우리는 아직 거기에 도착하지 않았습니다. – Miquella

    답변

    9

    색인을 잘못 이해했습니다. 인덱스를 사용 했더라도 200M 항목에서 인덱스 스캔을 수행합니다. 이것은 DISTINCT를 수행하는 데 걸린 시간을 더하여 오랜 시간이 걸릴 것이고, 실행하는 것은 나쁜 일입니다. 쿼리에서 DISTINCT를 보면 항상 빨간색 플래그가 발생하고 쿼리를 두 번 다시 확인하게됩니다. 이 경우 정규화 문제가있을 수 있습니까?

    +0

    의심의 여지가 있지만 이것은 부분적으로 데이터 정규화 문제 일 뿐이지 만 이전에 데이터를 정규화하면 성능 문제가 발생했습니다. 들어오는 데이터를 그대로 앞섰습니다. – Miquella

    +0

    인덱스 스캔,하지만 적어도 (이 경우) 인덱스를 스캔해서는 안 트리의 노드를 누르십시오, 잎을 통해 스캔하지? – Miquella

    +1

    인덱스가 심하게 조각화되어 스캔 시간이 길어질 수 있습니다. 유지 보수 작업을 실행합니까? 그 많은 데이터로 야간에 그렇게해야합니다. (시간을 정할 수 있다고 가정하십시오.) – beach

    0

    두 번째 쿼리는 1000000 레코드에서 작동하지만 첫 번째 쿼리는 200M에서 작동합니다. 나는 이것이 큰 차이라고 생각한다.

    +0

    그렇습니다. 큰 차이가 있습니다.하지만 첫 번째 쿼리가 인덱스를 사용하고 두 번째 쿼리가 테이블 스캔을 수행하기 때문에 차이가 반전되어야합니다. – Miquella

    1

    나의 첫 번째 생각은 통계이다. 마지막으로 업데이트 찾으려면 :

    SELECT 
        name AS index_name, 
        STATS_DATE(object_id, index_id) AS statistics_update_date 
    FROM 
        sys.indexes 
    WHERE 
        object_id = OBJECT_ID('MyTable'); 
    

    편집 : 인덱스가 재 빌드 될 때 통계가 업데이트되어, 내가

    내 두 번째 생각을 유지되지 않습니다를 참조하는 것은 그 여전히 인덱스입니다? TOP 쿼리는 여전히 인덱스를 사용해야합니다. 필자는 5 천 7 백만 행을 가진 테이블 중 하나에서 테스트했는데 둘 다 인덱스를 사용합니다.

    +0

    예, 인덱스가 있으며 인덱스를 사용하고 있습니다. :(확인한 첫 번째 항목은 인덱스를 스캔하는 것이지만 인덱스의 유일한 필드를 스캔하는 데 너무 오래 걸리는 이유는 모르겠습니다 ... – Miquella

    4

    SQL Server가 인덱스를 사용하려고하는 것조차 의심 스럽지만 좁은 테이블의 경우 거의 동일한 양의 작업을 수행해야합니다. 테이블 또는 인덱스의 여부에 관계없이 모든 200M 행을 읽어야합니다. . typeName의 색인이 클러스터 된 경우 그룹화 전에 정렬 할 필요가 없어 지므로 걸리는 시간이 줄어들 수 있습니다.

    유형의 카디널리티가 낮은 경우 뚜렷한 type 값의 목록을 보유하는 요약 테이블을 유지하는 것이 어떻습니까? 주 테이블의 삽입/갱신에 대한 트리거는 요약 테이블에 대한 점검을 수행하고 새 유형이 발견되면 새 레코드를 삽입합니다.

    +0

    +1; 삽입에 대한 트리거가 내가 생각했던 것보다 낫다. (주 테이블 다음에 두 번째 INSERT를 추가하고, 요약 테이블에 삽입하고, UNIQUE 제약 위반을 포착/무시). – kquinn

    +0

    나는 똑같은 생각을하고있었습니다. 개별 쿼리를 자주 실행해야하는 경우 요약 테이블을 수행하십시오. 또한 행을 제거한 후 테이블을 정리하기 위해 DELETE 트리거를 추가해야합니다. 그렇지 않으면 SQL 작업을 예약하여 야간에 요약 테이블을 업데이트하십시오. (삭제 된 타입 제거하기) – beach

    +0

    DELETE가 포함된다고 생각하면 요약 테이블에 참조 카운트 열이 있어야합니다. INSERT에서 트리거가 증가하고 DELETE에서 DELETE가 감소합니다. 그건 꽤 잘 작동합니다. – kquinn

    1

    다른 사람들이 이미 지적했듯이 테이블에 SELECT DISTINCT (typename)를 사용하면 어떤 테이블이든 상관없이 전체 테이블 스캔이 끝납니다.

    그래서 실제로 검사해야하는 행 수를 제한해야합니다.

    질문은 무엇입니까? DISTINCT 형식 이름은 무엇이 필요합니까? 200 만 행 중 몇 개가 뚜렷합니까? 소수의 typenames 만 몇 개 있습니까?

    그렇다면 별도의 테이블 DISTINCT_TYPENAMES 또는 다른 테이블을 가지고 전체 테이블 스캔을 수행 한 다음 주 테이블에 새 행을 삽입 할 때 해당 테이블 이름이 이미 DISTINCT_TYPENAMES에 있는지 확인해야합니다. 그렇지 않다면 추가하십시오.

    그런 식으로, 별개의 TypeName 항목 만있는 작은 테이블을 별도로 가질 수 있습니다.이 테이블은 쿼리 및/또는 표시하기에 번개가 빠릅니다.

    마크는

    +0

    테이블 스캔이 아닌 인덱스 스캔입니다 (이미 확인 했음). 인덱스가 올바르게 작성되면 전체 테이블이 아닌 인덱스를 스캔하는 것이 내 이해입니다. – Miquella

    +0

    테이블 대신 인덱스를 스캔 할 수 있고 스캔합니다. 그러나 이것은 인덱스가 해결하도록 설계된 문제가 아니므로 전체 인덱스 스캔은 전체 테이블 스캔보다이 쿼리를 상당히 빨리 해결할 수 없습니다. – kquinn

    +0

    인덱스에는 여전히 200M 개의 항목이 포함됩니다. –

    0

    나는 같은 것을 시도해야한다 :

    SELECT typeName FROM [types] WITH (nolock) 
    group by typeName; 
    

    을 그리고 난이 말을 다른처럼 당신은 열을 정상화 할 필요가있다.

    0

    인덱스를 사용하면 빠르게 행을 찾을 수 있습니다. 그러나 전체 테이블에 대한 모든 고유 한 유형을 나열하도록 데이터베이스에 요청합니다. 색인으로도 도움이되지 않습니다.

    쿼리를 실행하고 다른 테이블에 저장하는 야간 작업을 실행할 수 있습니다. 당신은 최신 데이터를 필요로하는 경우에는 야간 검사에 포함 된 마지막 ID를 저장하고, 결과를 결합 할 수 있습니다 :

    select type 
    from nightlyscan 
    union 
    select distinct type 
    from verybigtable 
    where rowid > lastscannedid 
    

    또 다른 옵션은 두 테이블에 큰 테이블을 정상화하는 것입니다

    talbe1: id, guid, typeid 
    type table: typeid, typename 
    

    유형 수가 비교적 적 으면이 방법이 매우 유용합니다.

    3

    DISTINCT 키워드를 사용할 때 SQL Server 최적화 프로그램에 isse가 있습니다. 솔루션은 별개의 쿼리를 별도로 분리하여 동일한 쿼리 계획을 유지하도록했습니다.

    그래서 우리는 너무 같은 질의 :

    SELECT DISTINCT [typeName] FROM [types] WITH (nolock); 
    

    그것을 해결하기 위해 다음과 같은

    SELECT typeName INTO #tempTable1 FROM types WITH (NOLOCK) 
    SELECT DISTINCT typeName FROM #tempTable1 
    

    또 다른 방법으로 헤어지고 다른 최적화 계획을 유도 할 수있는 GROUP BY을 사용하는 것입니다 .

    +1

    이 방법으로 실행 계획을 변경하는 방법에 대한 정보를 추가하는 것이 좋습니다. – EdC

    1

    루프 방식은 여러 번의 탐색을 사용해야하지만 (일부 병렬 처리는 손실됩니다). 전체 행 수 (낮은 카디널리티)와 비교할 때 비교적 적은 수의 고유 값을 가진 경우에 시도해 볼 가치가 있습니다.

    아이디어는이 question에서였다

    select typeName into #Result from Types where 1=0; 
    
    declare @t varchar(100) = (select min(typeName) from Types); 
    while @t is not null 
    begin 
        set @t = (select top 1 typeName from Types where typeName > @t order by typeName);  
        if (@t is not null) 
         insert into #Result values (@t); 
    end 
    
    select * from #Result; 
    

    또한 다른 방법 (특히 재귀 CTE @ 폴 화이트)가 다음과 같습니다

    different-ways-to-find-distinct-values-faster-methods

    sqlservercentral Topic873124-338-5

    +0

    예. 나는 또한 CTE 'skip-scan'[여기]에 대해 썼다. (http://sqlperformance.com/2014/10/t-sql-queries/performance-tuning-whole-plan) –

    0

    나는 뭔가를 놓칠 수 있지만 더 효율적인 경우로드 오버 헤드가있는보기를 만드는 경우 별개의 값과 쿼리 대신?

    결과 자체가 사소한보기의 특성을 감안할 때 결과 세트가 각 쓰기에 오버 헤드를 채우는 것보다 훨씬 작 으면 선택에 거의 즉각적인 응답을 제공합니다.

    당신이 할 때 속도의 뚜렷하고 중요성을 얼마나 자주 원하는지와 비교하여 얼마나 많은 쓰기가 필요한지 묻습니다.

    0

    인덱싱 된보기를 사용하면이 속도가 빨라질 수 있습니다.

    create view alltypes 
    with schemabinding as 
    select typename, count_big(*) as kount 
    from dbo.types 
    group by typename 
    
    create unique clustered index idx 
    on alltypes (typename) 
    

    온건해야 기본 테이블에 각 변경 사항에 대한 최신보기를 유지하는 일을 (응용 프로그램에 따라, 물론 - 나의 점은 전체 테이블을 스캔 할 필요가 없다는 것입니다 .

    select distinct typename 
    into alltypes 
    from types 
    
    alter table alltypes 
    add primary key (typename) 
    
    alter table types add foreign key (typename) references alltypes 
    

    외래 키가 사용되는 모든 값은 부모 alltypes 테이블에 표시해야 할 것입니다 : 때마다 나 같은 미친 듯이 비싼 아무것도 할)

    는 또한 모든 값을 들고 작은 테이블을 만들 수 있습니다. 문제는 alltypes이 아니고이 아니며 자식 types 테이블에 사용되지 않은 값을 포함하도록하는 것입니다.

    관련 문제