Только для 400 станций этот запрос будет значительно быстрее:
SELECT s.station_id, l.submitted_at, l.level_sensor
FROM station s
CROSS JOIN LATERAL (
SELECT submitted_at, level_sensor
FROM station_logs
WHERE station_id = s.station_id
ORDER BY submitted_at DESC NULLS LAST
LIMIT 1
) l;
dbfiddle здесь
(сравнение планов для этого запроса, альтернативы Abelisto и вашего оригинала)
В результате, EXPLAIN ANALYZE
как предусмотрено ФП:
Вложенный цикл (стоимость = 0.56..356.65 строк = 102 ширины = 20) (фактическое время = 0.034..0.979 строк = 98 циклов = 1)
-> Seq Scan на станциях s (стоимость = 0.00..3.02 строк = 102 ширины = 4) (фактическое время = 0.009..0.016 строк = 102 петли = 1)
-> Лимит (стоимость = 0.56..3.45 строк = 1 ширина = 16) (фактическое время = 0.009..0.009 строк = 1 цикл = 102)
-> Сканирование индекса с использованием station_id__submitted_at для station_logs (стоимость = 0,56..664062.38 строк = 230223 ширина = 16) (фактическое время = 0.009 $
Индекс Cond: (station_id = s.id)
Время планирования: 0,542 мс
Время выполнения: 1,013 мс - !!
Единственный индекс вам нужно , это один созданный Вами station_id__submitted_at
. UNIQUE
Ограничение uniq_sid_sat
также делает работу, в основном. Поддержание обоих кажется пустой тратой дискового пространства и производительностью записи.
Я добавил NULLS LAST
к ORDER BY
в запросе , потому что submitted_at
не определен NOT NULL
. В идеале, если применимо !, добавьте NOT NULL
ограничение к столбцу submitted_at
, удалите дополнительный индекс и удалите его NULLS LAST
из запроса.
Если submitted_at
возможно NULL
, создайте этот UNIQUE
индекс, чтобы заменить ваш текущий индекс и ограничение уникальности:
CREATE UNIQUE INDEX station_logs_uni ON station_logs(station_id, submitted_at DESC NULLS LAST);
Рассматривать:
Это предполагает отдельную таблицуstation
с одной строкой для каждого релевантного station_id
(обычно PK) - который вы должны иметь в любом случае. Если у вас его нет, создайте его. Опять же, очень быстро с этой техникой rCTE:
CREATE TABLE station AS
WITH RECURSIVE cte AS (
(
SELECT station_id
FROM station_logs
ORDER BY station_id
LIMIT 1
)
UNION ALL
SELECT l.station_id
FROM cte c
, LATERAL (
SELECT station_id
FROM station_logs
WHERE station_id > c.station_id
ORDER BY station_id
LIMIT 1
) l
)
TABLE cte;
Я использую это и в скрипке. Вы можете использовать аналогичный запрос для решения своей задачи напрямую, без station
таблицы - если вы не можете быть уверены, что создали ее.
Подробные инструкции, объяснения и альтернативы:
Оптимизировать индекс
Ваш запрос должен быть очень быстрым сейчас. Только если вам все еще нужно оптимизировать производительность чтения ...
Возможно, имеет смысл добавить level_sensor
последний индекс в индекс, чтобы разрешить сканирование только по индексу , как прокомментировал joanolo .
Con: Это делает индекс больше - что добавляет небольшую стоимость для всех запросов, использующих его.
Pro: Если вы на самом деле получаете только сканы индекса, запросу не нужно вообще посещать страницы кучи, что делает его примерно в два раза быстрее. Но это может быть несущественным преимуществом для очень быстрого запроса сейчас.
Однако я не ожидаю, что это сработает для вашего случая. Ты упомянул:
... около 20k строк в день на одного station_id
.
Как правило, это будет указывать на непрерывную загрузку записи (1 station_id
раз в 5 секунд). И вас интересует последний ряд. Сканирование только по индексу работает только для страниц кучи, видимых для всех транзакций (бит в карте видимости установлен). Вам нужно будет запустить чрезвычайно агрессивные VACUUM
настройки для таблицы, чтобы не отставать от нагрузки записи, и это все равно не будет работать большую часть времени. Если мои предположения верны, сканирование только по индексу отсутствует, не добавляйте level_sensor
к индексу.
OTOH, если мои предположения подтвердятся, а ваша таблица станет очень большой , индекс BRIN может помочь. Связанные с:
Или даже более специализированный и более эффективный: частичный индекс только для последних добавлений, чтобы отрезать большую часть ненужных строк:
CREATE INDEX station_id__submitted_at_recent_idx ON station_logs(station_id, submitted_at DESC NULLS LAST)
WHERE submitted_at > '2017-06-24 00:00';
Выберите временную метку, для которой вы знаете, что младшие строки должны существовать. Вы должны добавить WHERE
условие соответствия для всех запросов, например:
...
WHERE station_id = s.station_id
AND submitted_at > '2017-06-24 00:00'
...
Вы должны адаптировать индекс и запрос время от времени.
Связанные ответы с более подробной информацией: