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.