postgresql COUNT (DISTINCT…) 매우 느림


166

매우 간단한 SQL 쿼리가 있습니다.

SELECT COUNT(DISTINCT x) FROM table;

내 테이블에는 약 150 만 개의 행이 있습니다. 이 쿼리는 매우 느리게 실행됩니다. 약 7.5 초가 소요됩니다.

 SELECT COUNT(x) FROM table;

약 435ms가 걸립니다. 성능을 향상시키기 위해 쿼리를 변경하는 방법이 있습니까? 나는 x에 인덱스를 넣을뿐만 아니라 그룹화하고 규칙적인 수를 시도했습니다. 둘 다 동일한 7.5 초의 실행 시간을 갖습니다.


나는 그렇게 생각하지 않습니다. 150 만 행의 고유 한 값을 얻는 것은 느릴 것입니다.
— Ry-

5
방금 C #에서 시도했지만 메모리에서 150 만 개의 정수 값을 컴퓨터에서 1 초 이상 가져옵니다. 그래서 당신은 아마 운이 좋지 않다고 생각합니다.
— Ry-

쿼리 계획은 테이블 구조 (인덱스) 및 튜닝 상수 (작업) mem, effective_cache_size, random_page_cost의 설정에 따라 크게 달라집니다. 적절한 조정으로 쿼리를 1 초 이내에 실행할 수 있습니다.
— wildplasser 2018 년

좀 더 구체적으로 말씀해 주시겠습니까? 순식간에 얻으려면 어떤 인덱스와 튜닝 상수가 필요합니까? 간단히하기 위해 이것이 첫 번째 열 y에 기본 키가있는 2 열 테이블이라고 가정하고 int 유형의 두 번째 열 x에서 150 만 행 으로이 '고유 한'쿼리를 수행하고 있습니다.
— ferson2020

1
모든 색인과 함께 테이블 정의를 포함하고 ( \d출력 psql이 양호 함) 문제가있는 열을 정확하게 입력하십시오. EXPLAIN ANALYZE두 쿼리를 모두 보는 것이 좋습니다 .
— vyegorov

답변:


316

이것을 사용할 수 있습니다 :

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp;

이것은 다음보다 훨씬 빠릅니다.

COUNT(DISTINCT column_name)

38
거룩한 질문 배트맨! 이것은 내 postgres 수를 190에서 4.5로 바 꾸었습니다.
— rogerdpack

20
www.postgresql.org 에서이 스레드를 발견 했는데 같은 내용을 설명합니다 : link . 답글 중 하나 인 Jeff Janes는 COUNT (DISTINCT ())가 해시 대신 테이블을 정렬하여 작업을 수행한다고 말합니다.
— Ankur

5
@Ankur 질문 하나해도 될까요? COUNT(DISTINCT())정렬을 수행 하기 때문에 column_name특히 상대적으로 적은 양 work_mem(해시가 많은 양의 배치를 생성 하는 경우)에 대한 인덱스를 갖는 것이 도움이 될 것 입니다. COUNT (DISTINCT () _, 그렇지 않나요?)를 사용하는 것이 항상 나쁜 것은 아닙니다.
— St.Antario

2
@musmahn Count(column)은 null이 아닌 값만 계산합니다. count(*)행을 계산합니다. 따라서 첫 번째 / 더 긴 것은 null 행을 계산합니다 (한 번). count(column_name)동일하게 작동하도록 변경하십시오 .
— GolezTrol

1
@ankur 이것은 나에게별로 유용하지 않았습니다 .. 눈에 띄는 개선을 얻지 못했습니다.
— Shiwangini

11
-- My default settings (this is basically a single-session machine, so work_mem is pretty high)
SET effective_cache_size='2048MB';
SET work_mem='16MB';

\echo original
EXPLAIN ANALYZE
SELECT
        COUNT (distinct val) as aantal
FROM one
        ;

\echo group by+count(*)
EXPLAIN ANALYZE
SELECT
        distinct val
       -- , COUNT(*)
FROM one
GROUP BY val;

\echo with CTE
EXPLAIN ANALYZE
WITH agg AS (
    SELECT distinct val
    FROM one
    GROUP BY val
    )
SELECT COUNT (*) as aantal
FROM agg
        ;

결과 :

original                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1)
   ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1)
 Total runtime: 1766.642 ms
(3 rows)

group by+count(*)
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1)
   ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1)
         ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1)
 Total runtime: 412.686 ms
(4 rows)

with CTE
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1)
   CTE agg
     ->  HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1)
           ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1)
                 ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1)
       ->  CTE Scan on agg  (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1)
     Total runtime: 408.300 ms
    (7 rows)

CTE와 동일한 계획은 다른 방법으로도 생성 될 수 있습니다 (창 기능).


2
캐싱의 영향을 고려 했습니까? 세 번의 "설명 분석"을 수행하는 경우, 첫 번째는 디스크에서 항목을 느리게 가져 오는 반면 두 번째는 메모리에서 빨리 가져 오는 것입니다.
— tobixen 2016 년

실제로 : effective_cache_size는 첫 번째 조정 설정입니다. 광산은 2GB, IIRC입니다.
— wildplasser 2018 년

performance_cache_size를 2GB로 설정했는데 성능은 변경되지 않았습니다. 다른 설정을 조정 하시겠습니까? 그렇다면 무엇에?
— ferson2020

1) 어떻게 설정 했습니까? (HUP 했습니까?) 2) 실제로 사용 가능한 메모리가 있습니까? 3) 당신의 계획을 보여주십시오. 4) 어쩌면 내 컴퓨터가 더 빠르거나 더 많은 부하를 처리 할 수 ​​있습니다. @ ferson2020 : Ok
— wildplasser 2018 년

다음 명령문으로 설정했습니다. SET effective_cache_size = '2GB'; 사용 가능한 메모리가 충분합니다. 쿼리 계획을 포함하려고 시도했지만 주석 상자에 맞지 않습니다.
— ferson2020 2016 년

2

귀하의 경우 count(distinct(x))IS는 상당히 느린 것보다 count(x)당신은 예를 들어, 다른 테이블에서 x 값의 수를 유지함으로써이 쿼리 속도를 높일 수 있습니다 table_name_x_counts (x integer not null, x_count int not null), 트리거를 사용합니다. 그러나 쓰기 성능이 저하되고 x단일 트랜잭션에서 여러 값 을 업데이트하는 경우 교착 상태를 피하기 위해 명시적인 순서로이 작업을 수행해야합니다.


0

또한 어느 시점에서 limit / offset과 함께 고유 한 값을 가진 total_count가 필요했기 때문에 동일한 답변을 검색했습니다 .

한계 / 오프셋과 함께 고유 한 값으로 총 개수를 얻는 것이 약간 까다롭기 때문에. 일반적으로 한계 / 오프셋으로 총계를 구하기는 어렵습니다. 마침내 나는 할 길을 얻었다-

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

쿼리 성능도 높습니다.

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