postgresql COUNT (DISTINCT…) очень медленный


166

У меня очень простой запрос SQL:

SELECT COUNT(DISTINCT x) FROM table;

В моей таблице около 1,5 миллионов строк. Этот запрос выполняется довольно медленно; занимает около 7,5 с, по сравнению с

 SELECT COUNT(x) FROM table;

что занимает около 435 мс. Есть ли способ изменить мой запрос, чтобы повысить производительность? Я пытался группировать и делать регулярные подсчеты, а также поставить индекс на х; оба имеют одинаковое время выполнения 7.5 с.


Я так не думаю. Получение различных значений в 1,5 миллиона строк будет медленным.
Ry-

5
Я только что попробовал это в C #, получение на моем компьютере различных значений 1,5 миллиона целых чисел занимает более одной секунды. Поэтому я думаю, что вам, вероятно, не повезло.
Ry-

План запроса будет очень сильно зависеть от структуры таблицы (индексы) и настройки констант настройки (работа) mem ,ffective_cache_size, random_page_cost). При разумной настройке запрос может быть выполнен менее чем за секунду.
wildplasser

Не могли бы Вы уточнить? Какие индексы и константы настройки потребуются, чтобы получить его за секунду? Для простоты предположим, что это таблица из двух столбцов с первичным ключом в первом столбце y, и я делаю этот «отдельный» запрос для второго столбца x типа int с 1,5 миллионами строк.
ferson2020

1
Пожалуйста, включите определение таблицы со всеми индексами ( \dвывод psql- хороший) и укажите столбец, с которым у вас возникли проблемы. Было бы хорошо увидеть EXPLAIN ANALYZEоба запроса.
Выгоров

Ответы:


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, где обсуждается то же самое: ссылка . В одном из ответов (Джефф Джейнс) говорится, что 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 это было не очень полезно для меня ... не получило каких-либо заметных улучшений.
Шивангини

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

В самом деле :ффективная_система_сайза - это первая настройка для настройки. Мой 2 ГБ, IIRC.
wildplasser

Я установил для параметраative_cache_size значение 2 ГБ без изменения производительности. Любые другие настройки, которые вы бы предложили настроить? Если так, то к чему?
ferson2020

1) как вы это установили? (Вы сделали это?) 2) У вас действительно так много памяти? 3) покажи нам свой план. 4) возможно, моя машина работает быстрее, или у вас больше параллельной загрузки. @ ferson2020: Хорошо
дикий человек

Я установил это с помощью инструкции: SETffective_cache_size = '2GB'; У меня так много памяти доступно. Я попытался включить мой план запроса, но он не помещается в поле для комментариев.
ferson2020

2

Если ваш запрос count(distinct(x))значительно медленнее, чем count(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.