Как ускорить выделение отличного?

16

У меня есть простой выбор различных данных временных рядов:

SELECT DISTINCT user_id
FROM events
WHERE project_id = 6
AND time > '2015-01-11 8:00:00'
AND time < '2015-02-10 8:00:00';

И это занимает 112 секунд. Вот план запроса:

http://explain.depesz.com/s/NTyA

Мое приложение должно предварительно выполнить множество различных операций и рассчитывать, как это. Есть ли более быстрый способ получить такие данные?

Сэм
источник

Ответы:

19

Вы, вероятно, не хотите слышать это, но лучший способ ускорить это SELECT DISTINCT- избегать DISTINCT с самого начала. Во многих случаях (не во всех!) Этого можно избежать с помощью лучшего дизайна базы данных или лучших запросов.

Иногда GROUP BYэто быстрее, потому что он использует другой путь кода.

В вашем конкретном случае это не похоже, что вы можете избавиться от DISTINCT. Но вы можете поддержать запрос с помощью специализированного индекса, если у вас много запросов такого рода:

CREATE INDEX foo ON events (project_id, "time", user_id);

Добавление user_idполезно только в том случае, если вы получаете только сканирование по индексу . Перейдите по ссылке для получения подробной информации. Удаляет дорогостоящее растровое сканирование кучи из вашего плана запросов, который потребляет 90% времени запроса.

Ваш EXPLAINвывод говорит мне, что запрос должен сгущать 2,491 различных пользователей из полумиллиона совпадающих строк. Это не станет очень быстрым, независимо от того, что вы делаете, но это может быть существенно быстрее.

Если временные интервалы в ваших запросах всегда одинаковы, MATERIALIIZED VIEWсворачивание user_idв (project_id, <fixed time intervall>)большую сторону будет иметь большое значение. Там нет шансов с различными временными интервалами, хотя. Может быть, вы могли бы по крайней мере сбрасывать пользователей в час или какое-то другое минимальное время, и это купило бы производительность, достаточную для значительных накладных расходов.

Nitpick:
Скорее всего, предикаты "time"должны быть:

AND "time" >= '2015-01-11 8:00:00'
AND "time" <  '2015-02-10 8:00:00';

В сторону:
не используйте в timeкачестве идентификатора. Это зарезервированное слово в стандартном SQL и базовый тип в Postgres.

Эрвин Брандштеттер
источник
Я прочитал немного о сканировании только по индексу, я попробую.
Сэм
К сожалению, временной интервал не фиксирован.
Сэм
@Sam: Итак, насколько быстрее ваш пример запроса получился с предложенным индексом?
Эрвин Брандштеттер
3
@edwin: Еще не пробовал на производстве. Однако я выполнил исходный запрос на локальном сервере (с теми же данными), и это заняло 3678,780 мс. Затем я добавил индекс, и он ускорился до 170,156 мс. План теперь содержит «Сканирование только по индексу с использованием foo on events».
Сэм
1
@ Сэм: Хорошо! Вот к чему я стремился.
Эрвин Брандштеттер
2

Вот мой тест по делу Сэма и ответ Эрвина

drop table t1
create table t1 (id int, user_id int, project_id int, date_time timestamp without time zone) ;

insert into t1 -- 10 million row - size="498 MB"
select row_number() over(), round(row_number() over()/1000), round(row_number() over()/100000) , date
from generate_series('2015-01-01'::date, '2016-12-01'::date,'6 seconds'::interval
) date 
limit 10000000

-- before indexing - 10000000 row - output=100 row - time=2900ms
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 8:00:00'
AND date_time < '2016-12-01 8:00:00' ;

CREATE INDEX foo ON t1 (project_id, date_time, user_id); -- time process=51.2 secs -- size="387 MB"         

-- after indexing - 10000000 row - output=100 row - time= 75ms (reduce ~ 38 times)
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 00:00:00'
AND date_time < '2016-12-01 00:00:00' ;

Эрвин сказал: «Вы, вероятно, не хотите этого слышать, но лучший способ ускорить SELECT DISTINCT - это сначала избегать DISTINCT. Во многих случаях (не во всех!) Этого можно избежать с помощью лучшего проектирования базы данных или лучших запросов. " Я думаю, что он прав, мы должны избегать использования «различного, группового, упорядоченного по» (если есть).

Я встречался с ситуацией Сэма, и я думаю, что Сэм может использовать раздел таблицы событий по месяцам. Это уменьшит размер ваших данных при запросе, но вам нужна функция (pl / pgsql) для выполнения вместо запроса выше. Функция найдет соответствующие разделы (в зависимости от условий) для выполнения запроса.

Луан Хуинх
источник
2
> Я думаю, что он прав, мы должны избегать использования «отличного, сгруппированного, упорядоченного по», а также SELECT, INSERT и UPDATE. Если мы избежим этих конструкций, наша база данных будет очень быстрой!
Greatvovan