#title COUNT(DISTINCT)성능 [[TableOfContents]] ==== 개요 ==== count(distinct )는 느린경우가 많다. 어떤 경우에 느리고, SQL Server의 버전에 따른 차이는 어떤지 살펴보자. 일단 테스트 데이터를 만들자. {{{ if object_id('tempdb.dbo.#temp') is not null drop table #temp ;with temp as ( select * from (values (1),(2),(3),(4),(5),(6),(7)) t(seq) ) select a.seq id , (b.seq + c.seq + d.seq + f.seq) % 3 grp , b.seq + c.seq + d.seq + f.seq val into #temp from temp a cross join temp b cross join temp c cross join temp d cross join temp f }}} 참고로 set statistics io on을 하면 임시테이블은 '#temp_______________________________________________________________________________________________________________000000015577'와 같은 형식으로 나오는데 '#temp'로 나오게 끔 임의로 지웠다. ==== 비교1 ==== 그리고 다음과 같이 count(distinct )를 해보자. {{{ select count(distinct id) distinct_count from #temp }}} attachment:p1.png 실행계획은 2008과 2012나 차이가 없다. ==== 비교2 ==== {{{ select sum(val) amt , count(distinct id) distinct_count from #temp }}} 2008 attachment:p2.png {{{ 테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 2, 논리적 읽기 수 90, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} 2012 attachment:p3.png {{{ 테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 1, 논리적 읽기 수 44, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} ==== 비교3 ==== {{{ select grp , sum(val) amt , count(distinct id) distinct_count from #temp group by grp }}} 2008 attachment:p4.png {{{ 테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 2, 논리적 읽기 수 90, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} 2012 attachment:p5.png {{{ 테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 1, 논리적 읽기 수 44, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} ==== 비교4 ==== {{{ select grp , sum(val) amt , count(distinct id) distinct_count from #temp where id <= 10 group by grp }}} 2008 attachment:p6.png {{{ 테이블 'Worktable'. 검색 수 2, 논리적 읽기 수 34038, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 1, 논리적 읽기 수 45, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} '''아.. 2008버전은 병신같다. ''' 2012 attachment:p7.png {{{ 테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 1, 논리적 읽기 수 44, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} ==== 결론 ==== 집계시 COUNT(DISTINCT)가 단독으로 쓰이지 않고 다른 집계함수(COUNT(DISTINCT)를 2회 이상 사용도 포함)와 같이 쓰이면 상황에 따라 다르지만 적어도 2배 이상의 i/o가 발생한다. 가장 최악의 상황은 COUNT(DISTINCT)가 다른 집계함수와 같이 쓰이고, where절에 조건이 명시된 SQL이 실행되는 상황이다. 이 경우 COUNT(DISTINCT)가 명시된 횟수만큼 [Spool]이발생한다. [Spool]은 임시적인 연산을 위해서 tempdb에 쓰고, 읽는 연산이다. 그러므로 더 많은 IO가 발생하게 된다. 여기까지가 2008 이야기다. 2012는 COUNT(DISTINCT)시 반복되는 연산을 없애 주었지만, 이것도 COUNT(DISTINCT)가 1회만 같이 사용된 상황만을 해결했다. 다음의 SQL을 보자. {{{ select sum(val) amt , count(distinct id) distinct_count , count(distinct grp) distinct_count from #temp where id <= 10 --이 조건을 빼면 spool이 아닌 table scan이 3회 일어난다. }}} attachment:p8.png {{{ 테이블 'Worktable'. 검색 수 3, 논리적 읽기 수 34100, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 1, 논리적 읽기 수 44, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} 조금 다른 방법을 살펴보자. 2012버전에는 columnstore index를 생성할 수 있다. columnstore index생성 하고 SQL을 실행해보자. {{{ create nonclustered columnstore index ncix_01 on #temp ( id , grp , val ) select sum(val) amt , count(distinct id) distinct_count , count(distinct grp) distinct_count from #temp }}} attachment:p9.png {{{ 테이블 'Worktable'. 검색 수 0, 논리적 읽기 수 0, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 3, 논리적 읽기 수 26, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} i/o가 많이 줄었다. 그렇다면 where 조건을 준다면 어떻게 동작할까? {{{ select sum(val) amt , count(distinct id) distinct_count , count(distinct grp) distinct_count from #temp where id <= 10 }}} attachment:p10.png {{{ 테이블 'Worktable'. 검색 수 3, 논리적 읽기 수 34100, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. 테이블 '#temp'. 검색 수 1, 논리적 읽기 수 26, 물리적 읽기 수 0, 미리 읽기 수 0, LOB 논리적 읽기 수 0, LOB 물리적 읽기 수 0, LOB 미리 읽기 수 0. }}} '''음..''' '''2012도 여전히 병신같다.''' 여전히 count(distinct)집계는 ssis를 이용하는 것이 상황 고려를 안해도 되는 단순한 솔루션이 되겠다. CLR을 이용하는 방법도 있다. [http://databaser.net/moniwiki/wiki.php/DistinctCount 여기]를 보면 CLR 집계함수를 이용하는 방법이 나와 있는데, 메모리가 충분해야 하고, 어떤 함수는 int만을 매개변수로 받는 제약이 있다.