Я пытаюсь определить, какие индексы использовать для запроса SQL с WHERE
условием, а GROUP BY
который в настоящее время работает очень медленно.
Мой запрос:
SELECT group_id
FROM counter
WHERE ts between timestamp '2014-03-02 00:00:00.0' and timestamp '2014-03-05 12:00:00.0'
GROUP BY group_id
В настоящее время таблица содержит 32 000 000 строк. Время выполнения запроса значительно увеличивается, когда я увеличиваю временные рамки.
Данная таблица выглядит следующим образом:
CREATE TABLE counter (
id bigserial PRIMARY KEY
, ts timestamp NOT NULL
, group_id bigint NOT NULL
);
В настоящее время у меня есть следующие индексы, но производительность все еще низкая:
CREATE INDEX ts_index
ON counter
USING btree
(ts);
CREATE INDEX group_id_index
ON counter
USING btree
(group_id);
CREATE INDEX comp_1_index
ON counter
USING btree
(ts, group_id);
CREATE INDEX comp_2_index
ON counter
USING btree
(group_id, ts);
Запуск EXPLAIN для запроса дает следующий результат:
"QUERY PLAN"
"HashAggregate (cost=467958.16..467958.17 rows=1 width=4)"
" -> Index Scan using ts_index on counter (cost=0.56..467470.93 rows=194892 width=4)"
" Index Cond: ((ts >= '2014-02-26 00:00:00'::timestamp without time zone) AND (ts <= '2014-02-27 23:59:00'::timestamp without time zone))"
SQL Fiddle с примерами данных: http://sqlfiddle.com/#!15/7492b/1
Вопрос
Можно ли повысить производительность этого запроса, добавив лучшие индексы, или я должен увеличить вычислительную мощность?
Редактировать 1
PostgreSQL версии 9.3.2 используется.
Редактировать 2
Я попробовал предложение @Erwin с EXISTS
:
SELECT group_id
FROM groups g
WHERE EXISTS (
SELECT 1
FROM counter c
WHERE c.group_id = g.group_id
AND ts BETWEEN timestamp '2014-03-02 00:00:00'
AND timestamp '2014-03-05 12:00:00'
);
Но, к сожалению, это не увеличило производительность. План запроса:
"QUERY PLAN"
"Nested Loop Semi Join (cost=1607.18..371680.60 rows=113 width=4)"
" -> Seq Scan on groups g (cost=0.00..2.33 rows=133 width=4)"
" -> Bitmap Heap Scan on counter c (cost=1607.18..158895.53 rows=60641 width=4)"
" Recheck Cond: ((group_id = g.id) AND (ts >= '2014-01-01 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
" -> Bitmap Index Scan on comp_2_index (cost=0.00..1592.02 rows=60641 width=0)"
" Index Cond: ((group_id = g.id) AND (ts >= '2014-01-01 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
Редактировать 3
План запроса для LATERAL-запроса из ypercube:
"QUERY PLAN"
"Nested Loop (cost=8.98..1200.42 rows=133 width=20)"
" -> Seq Scan on groups g (cost=0.00..2.33 rows=133 width=4)"
" -> Result (cost=8.98..8.99 rows=1 width=0)"
" One-Time Filter: ($1 IS NOT NULL)"
" InitPlan 1 (returns $1)"
" -> Limit (cost=0.56..4.49 rows=1 width=8)"
" -> Index Only Scan using comp_2_index on counter c (cost=0.56..1098691.21 rows=279808 width=8)"
" Index Cond: ((group_id = $0) AND (ts IS NOT NULL) AND (ts >= '2010-03-02 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
" InitPlan 2 (returns $2)"
" -> Limit (cost=0.56..4.49 rows=1 width=8)"
" -> Index Only Scan Backward using comp_2_index on counter c_1 (cost=0.56..1098691.21 rows=279808 width=8)"
" Index Cond: ((group_id = $0) AND (ts IS NOT NULL) AND (ts >= '2010-03-02 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
group_id
значений есть в таблице?group_id
а не в каком количестве?Ответы:
Другая идея, которая также использует
groups
таблицу и конструкцию, называемуюLATERAL
join (для фанатов SQL-Server это почти идентичноOUTER APPLY
). Преимущество состоит в том, что агрегаты могут быть вычислены в подзапросе:Тест в SQL-Fiddle показывает, что запрос выполняет сканирование индекса по
(group_id, ts)
индексу.Аналогичные планы создаются с использованием 2 боковых объединений, одного для минимального и одного для максимального, а также с 2 встроенными коррелированными подзапросами. Их также можно использовать, если вам нужно показать целые
counter
строки, кроме минимальной и максимальной дат:источник
Поскольку у вас нет агрегата в списке выбора, то
group by
это почти то же самое, что и включениеdistinct
в список выбора, верно?Если это именно то, что вам нужно, вы можете быстро найти индекс comp_2_index, переписав его для использования рекурсивного запроса, как описано в вики PostgreSQL .
Создайте представление для эффективного возврата различных group_ids:
А затем используйте это представление вместо таблицы поиска в полусоединении Эрвина
exists
.источник
Поскольку есть только
133 different group_id's
, вы можете использоватьinteger
(или дажеsmallint
) для group_id. Однако это не принесет вам большой пользы, поскольку заполнение до 8 байт съест остаток вашей таблицы и возможные многоколонные индексы. Обработка равниныinteger
должна быть немного быстрее, хотя. Больше наint
противint2
.@Leo: временные метки хранятся как 8-байтовые целые числа в современных установках и могут обрабатываться совершенно быстро. Детали.
@ypercube: индекс on
(group_id, ts)
не может помочь, так какgroup_id
в запросе нет условия on .Ваша главная проблема - это огромный объем данных, которые необходимо обработать:
Я вижу, вы заинтересованы только в существовании
group_id
, а не в фактическом количестве. Кроме того, есть только 133 различныхgroup_id
с. Поэтому ваш запрос может быть удовлетворен первым попаданием заgorup_id
период времени. Отсюда это предложение для альтернативного запроса сEXISTS
полусоединением :Предполагая таблицу поиска для групп:
Ваш индекс
comp_2_index
на(group_id, ts)
становится инструментальным сейчас.SQL Fiddle (сборка на скрипке, предоставленной @ypercube в комментариях)
Здесь запрос предпочитает индекс
(ts, group_id)
, но я думаю, что это связано с настройкой теста с «кластерными» временными метками. Если вы удалите индексы с ведущимts
( подробнее об этом ), планировщик также с радостью будет использовать индекс(group_id, ts)
- особенно при сканировании только по индексу .Если это работает, вам может не понадобиться это другое возможное улучшение: Предварительная агрегация данных в материализованном представлении для существенного сокращения количества строк. Это имело бы смысл , в частности, если необходимо также фактические счетчики дополнительно. Тогда у вас есть стоимость, чтобы обработать много строк один раз при обновлении mv. Вы даже можете комбинировать ежедневные и почасовые агрегаты (две отдельные таблицы) и адаптировать свой запрос к этому.
Являются ли временные рамки в ваших запросах произвольными? Или в основном на полных минутах / часах / днях?
Создайте необходимые индексы
counter_mv
и адаптируйте свой запрос для работы с ним ...источник
groups
таблицы?ANALYZE
имеет значение. Но индексыcounter
даже привыкнут,ANALYZE
как только я представлюgroups
таблицу. Дело в том, что без этой таблицы в любом случае необходим seqscan для построения набора возможных group_id. Я добавил больше к своему ответу. И спасибо за вашу скрипку!group_id
даже дляSELECT DISTINCT group_id FROM t;
запроса?LIMIT 1
него можно выбрать сканирование индекса растрового изображения, которое не выигрывает от ранней остановки и занимает намного больше времени. (Но если таблица только что очищена пылесосом, она может предпочесть сканирование только по индексу, а не растровое сканирование, поэтому поведение, которое вы видите, зависит от состояния вакуума в таблице).