2016-04-22 2 views
2

У меня есть таблица событий с 30 миллионами строк. Следующий запрос возвращает в 25 секундМедленный запрос PostgreSQL с (неверными?) Индексами

SELECT DISTINCT "events"."id", "calendars"."user_id" 
FROM "events" 
LEFT JOIN "calendars" ON "events"."calendar_id" = "calendars"."id" 
WHERE "events"."deleted_at" is null 
AND tstzrange('2016-04-21T12:12:36-07:00', '2016-04-21T12:22:36-07:00') @> lower(time_range) 
AND ("status" is null or (status->>'pre_processed') IS NULL) 

status является jsonb колонки с индексом на status->>'pre_processed'. Вот другие индексы, которые были созданы в таблице событий. time_range имеет тип TSTZRANGE.

CREATE INDEX events_time_range_idx ON events USING gist (time_range); 
CREATE INDEX events_lower_time_range_index on events(lower(time_range)); 
CREATE INDEX events_upper_time_range_index on events(upper(time_range)); 
CREATE INDEX events_calendar_id_index on events (calendar_id) 

Я определенно вышел из своей зоны комфорта и стараюсь сократить время запроса. Вот выход объяснить анализ

HashAggregate (cost=7486635.89..7486650.53 rows=1464 width=48) (actual time=26989.272..26989.306 rows=98 loops=1) 
    Group Key: events.id, calendars.user_id 
    -> Nested Loop Left Join (cost=0.42..7486628.57 rows=1464 width=48) (actual time=316.110..26988.941 rows=98 loops=1) 
    -> Seq Scan on events (cost=0.00..7475629.43 rows=1464 width=50) (actual time=316.049..26985.344 rows=98 loops=1) 
      Filter: ((deleted_at IS NULL) AND ((status IS NULL) OR ((status ->> 'pre_processed'::text) IS NULL)) AND ('["2016-04-21 19:12:36+00","2016-04-21 19:22:36+00")'::tstzrange @> lower(time_range))) 
      Rows Removed by Filter: 31592898 
    -> Index Scan using calendars_pkey on calendars (cost=0.42..7.50 rows=1 width=48) (actual time=0.030..0.031 rows=1 loops=98) 
      Index Cond: (events.calendar_id = (id)::text) 
Planning time: 1.468 ms 
Execution time: 26989.370 ms 

А вот объяснить анализ с events.deleted_at части запроса удаляют,

HashAggregate (cost=7487382.57..7487398.33 rows=1576 width=48) (actual time=23880.466..23880.503 rows=115 loops=1) 
    Group Key: events.id, calendars.user_id 
    -> Nested Loop Left Join (cost=0.42..7487374.69 rows=1576 width=48) (actual time=16.612..23880.114 rows=115 loops=1) 
    -> Seq Scan on events (cost=0.00..7475629.43 rows=1576 width=50) (actual time=16.576..23876.844 rows=115 loops=1) 
      Filter: (((status IS NULL) OR ((status ->> 'pre_processed'::text) IS NULL)) AND ('["2016-04-21 19:12:36+00","2016-04-21 19:22:36+00")'::tstzrange @> lower(time_range))) 
      Rows Removed by Filter: 31592881 
    -> Index Scan using calendars_pkey on calendars (cost=0.42..7.44 rows=1 width=48) (actual time=0.022..0.023 rows=1 loops=115) 
      Index Cond: (events.calendar_id = (id)::text) 

время планирования: 0,372 мс Время Исполнение: 23880.571 мс

I добавили индекс в столбец status. Все остальное, что уже есть, и я не уверен, как двигаться дальше. Любые предложения о том, как получить время запроса до более управляемого номера?

+1

Структуры событий и таблицы календарей будет полезно. Если вы можете опубликовать результат анализа объяснения, а не просто объяснение, которое может помочь. – e4c5

+0

@ e4c5 Спасибо. Я добавил анализ объяснений. Я могу добавить структуру позже. Я упомянул поля, на которые я обращаюсь, это TSTZRANGE и JSONB. deleted_at - это только временная метка – FajitaNachos

+0

Не уверен, что вам нужно '@> lower (time_range)' не будет «перекрывать» то же самое? 'where ... @> time_range' - который может использовать индекс gist для этого столбца. Также какое из условий удаляет большую часть строк? Условие на 'status', то есть на' time_range' или на 'deleted_at'? –

ответ

3

Индекс В-дерева на lower(time_range) может быть использован только для состояний, включающих операторы <, <=, =, >= и >. Оператор @> может полагаться на них внутренне, но, насколько это касается планировщика, эта операция проверки диапазона - это черный ящик, и поэтому он не может использовать индекс.

Вам нужно будет переформулировать свое состояние в терминах операторов B-дерева, т.е .:

lower(time_range) >= '2016-04-21T12:12:36-07:00' AND 
lower(time_range) < '2016-04-21T12:22:36-07:00' 
+0

Ты мужчина. 68 мс с этим небольшим изменением. Я подозревал, что это связано с тем, как я обращался к временному диапазону, но я довольно из моего элемента здесь. Теперь он использует индексную проверку «Индексное сканирование с использованием событий_lower_time_range_index для событий» (стоимость = 0.57..2177.94 rows = 5 width = 50) (фактическое время = 0.019..0.186 rows = 98 loops = 1) '. В нем говорится, что я могу наградить вас щедростью за 2 часа. Благодаря! – FajitaNachos

+0

В первый раз, когда я меняю временной диапазон, он принимает значительно и изменчиво длиннее (10 с, 30 с, 16 с), но все последующие запросы на этом временном диапазоне составляют <1 с. Есть ли другие настройки, которые могут помочь избежать этого, или это только то, как работают сотрудники Postgres? – FajitaNachos

+0

Возможно, это связано с кэшированием; первый запрос должен попасть на диск, но последующие запросы находят все данные в ОЗУ. Увеличение размера кеша (путем повышения ['shared_buffers'] (http://www.postgresql.org/docs/current/static/runtime-config-resource.html#GUC-SHARED-BUFFERS) и/или добавления большего количества ОЗУ) может немного помочь, но нет серебряной пули. –

0

Поэтому добавьте индекс для events.deleted_at, чтобы избавиться от неприятного последовательного сканирования. Как это выглядит после этого?

+0

Чтобы добавить индекс в эту таблицу, требуется некоторое время, поэтому в то же время я просто удалил события «WHERE». «Deleted_at» имеет значение null, и запрос все равно возвращается за такое же количество времени. Написал этот анализ. Похоже, последовательное сканирование все еще работает. – FajitaNachos

Смежные вопросы