postgresql COUNT (DISTINCT…) дуже повільно


166

У мене дуже простий SQL-запит:

SELECT COUNT(DISTINCT x) FROM table;

Мій стіл нараховує близько 1,5 мільйонів рядків. Цей запит працює досить повільно; це займає приблизно 7,5s, порівняно з

 SELECT COUNT(x) FROM table;

що займає близько 435 мс. Чи можна змінити запит для підвищення ефективності? Я спробував групувати та робити регулярний підрахунок, а також ставити індекс на x; обидва мають однаковий час виконання 7,5s.


Я не думаю, що так. Отримати чіткі значення 1,5 мільйона рядків буде просто повільно.
Ри-

5
Я щойно спробував це на C #, отримуючи від пам'яті чіткі значення 1,5 мільйона цілих чисел, що займає одну секунду на моєму комп’ютері. Тому я думаю, що вам, мабуть, не пощастило.
Ри-

План запитів буде дуже залежати від структури таблиці (індексів) та налаштування констант настройки (робочої) пам’яті, ефективної_cache_size, random_page_cost). При розумній настройці запит, можливо, може бути виконаний менше ніж за секунду.
wildplasser

Чи можете ви бути більш конкретними? Які індекси та константи настройки потрібні, щоб отримати їх за секунду? Для простоти припустімо, що це таблиця з двома стовпцями з первинним ключем у першому стовпчику y, і я роблю цей "виразний" запит на другому стовпчику x типу int з 1,5 мільйонами рядків.
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
святі запити Бетмена! Це прискорило моє число поштових розрядів від 190-х до 4,5-ох!
rogerdpack

20
Я знайшов цю тему на www.postgresql.org, де обговорюється те саме: посилання . В одній з відповідей (Джефф Джейнс) сказано, що COUNT (DISTINCT ()) сортує таблицю, щоб виконувати свою роботу, а не використовувати хеш.
Анкур

5
@Ankur Чи можу я задати вам питання? Оскільки COUNT(DISTINCT())проводиться сортування, то, безумовно, корисно буде мати індекс column_nameособливо з відносно невеликою кількістю work_mem(де хешування дасть відносно велику кількість партій). Оскільки це не завжди погано використовувати COUNT (DISTINCT () _, чи не так?
St.Antario

2
@musmahn Count(column)підраховує лише ненульові значення. count(*)рахує рядки. Отже, перший / довший, також буде рахувати нульовий рядок (один раз). Змініть, щоб 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

Дійсно: efect_cache_size - це перша настройка, яку потрібно налаштувати. Моя 2 Гб, IIRC.
wildplasser

Я встановив ефективний_cache_size на 2 ГБ, без зміни продуктивності. Будь-які інші налаштування, які ви б запропонували налаштувати? Якщо так, то до чого?
ferson2020

1) як ви його встановили? (Ви це HUP?) 2) Чи є у вас насправді стільки пам’яті? 3) покажіть нам свій план. 4) можливо моя машина швидша, або у вас є більше одночасного навантаження. @ ferson2020: Гаразд
wildplasser

Я встановив це за допомогою оператора: SET efektiv_cache_size = '2 ГБ'; У мене є стільки пам’яті. Я спробував включити свій план запитів, але він не впишеться у поле коментарів.
ferson2020

2

Якщо ваш процес count(distinct(x))значно повільніший, ніж count(x)тоді, ви можете прискорити цей запит, підтримуючи кількість значень x в іншій таблиці, наприклад table_name_x_counts (x integer not null, x_count int not null), використовуючи тригери. Але ваша ефективність запису постраждає, і якщо ви оновлюєте кілька xзначень в одній транзакції, вам знадобиться зробити це в явному порядку, щоб уникнути можливого глухого кута.


0

Я також шукав ту саму відповідь, тому що в якийсь момент мені знадобився total_count з чіткими значеннями разом з лімітом / зміщенням .

Оскільки це зробити нелегко - отримати загальний підрахунок з чіткими значеннями разом з обмеженням / зміщенням. Зазвичай важко отримати загальний підрахунок з обмеженням / зміщенням. Нарешті я отримав спосіб зробити -

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

Продуктивність запитів також висока.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.