У меня есть таблица station_logs
в базе данных PostgreSQL 9.6:
Column | Type |
---------------+-----------------------------+
id | bigint | bigserial
station_id | integer | not null
submitted_at | timestamp without time zone |
level_sensor | double precision |
Indexes:
"station_logs_pkey" PRIMARY KEY, btree (id)
"uniq_sid_sat" UNIQUE CONSTRAINT, btree (station_id, submitted_at)
Я пытаюсь получить последнее level_sensor
значение на основе submitted_at
каждого station_id
. Есть около 400 уникальных station_id
значений и около 20 тысяч строк в день station_id
.
Перед созданием индекса:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Уникальный (стоимость = 4347852.14..4450301.72 строки = 89 ширина = 20) (фактическое время = 22202.080..27619.167 строк = 98 циклов = 1) -> Сортировка (стоимость = 4347852.14..4399076.93 строки = 20489916 ширина = 20) (фактическое время = 22202.077..26540.827 строк = 20489812 циклов = 1) Ключ сортировки: station_id, submit_at DESC Метод сортировки: внешний диск слияния: 681040kB -> Seq Scan на station_logs (стоимость = 0.00..598895.16 строк = 20489916 ширина = 20) (фактическое время = 0.023..3443.587 строк = 20489812 циклов = $ Время планирования: 0,072 мс Время выполнения: 27690,644 мс
Создание индекса:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
После создания индекса для того же запроса:
Уникальный (стоимость = 0.56..2156367.51 строк = 89 ширина = 20) (фактическое время = 0.184..16263.413 строк = 98 циклов = 1) -> Сканирование индекса с использованием station_id__submitted_at для station_logs (стоимость = 0,56..2105142.98 строк = 20489812 ширина = 20) (фактическое время = 0.181..1 $ Время планирования: 0,206 мс Время выполнения: 16263,490 мс
Есть ли способ сделать этот запрос быстрее? Как например, 1 секунда, 16 секунд все еще слишком много.
Ответы:
Только для 400 станций этот запрос будет значительно быстрее:
dbfiddle здесь
(сравнение планов для этого запроса, альтернативы Abelisto и вашего оригинала)
В результате,
EXPLAIN ANALYZE
как предусмотрено ФП:Единственный индекс вам нужно , это один созданный Вами
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
индекс, чтобы заменить ваш текущий индекс и ограничение уникальности:Рассматривать:
Это предполагает отдельную таблицу
station
с одной строкой для каждого релевантногоstation_id
(обычно PK) - который вы должны иметь в любом случае. Если у вас его нет, создайте его. Опять же, очень быстро с этой техникой rCTE:Я использую это и в скрипке. Вы можете использовать аналогичный запрос для решения своей задачи напрямую, без
station
таблицы - если вы не можете быть уверены, что создали ее.Подробные инструкции, объяснения и альтернативы:
Оптимизировать индекс
Ваш запрос должен быть очень быстрым сейчас. Только если вам все еще нужно оптимизировать производительность чтения ...
Возможно, имеет смысл добавить
level_sensor
последний индекс в индекс, чтобы разрешить сканирование только по индексу , как прокомментировал joanolo .Con: Это делает индекс больше - что добавляет небольшую стоимость для всех запросов, использующих его.
Pro: Если вы на самом деле получаете только сканы индекса, запросу не нужно вообще посещать страницы кучи, что делает его примерно в два раза быстрее. Но это может быть несущественным преимуществом для очень быстрого запроса сейчас.
Однако я не ожидаю, что это сработает для вашего случая. Ты упомянул:
Как правило, это будет указывать на непрерывную загрузку записи (1
station_id
раз в 5 секунд). И вас интересует последний ряд. Сканирование только по индексу работает только для страниц кучи, видимых для всех транзакций (бит в карте видимости установлен). Вам нужно будет запустить чрезвычайно агрессивныеVACUUM
настройки для таблицы, чтобы не отставать от нагрузки записи, и это все равно не будет работать большую часть времени. Если мои предположения верны, сканирование только по индексу отсутствует, не добавляйтеlevel_sensor
к индексу.OTOH, если мои предположения подтвердятся, а ваша таблица станет очень большой , индекс BRIN может помочь. Связанные с:
Или даже более специализированный и более эффективный: частичный индекс только для последних добавлений, чтобы отрезать большую часть ненужных строк:
Выберите временную метку, для которой вы знаете, что младшие строки должны существовать. Вы должны добавить
WHERE
условие соответствия для всех запросов, например:Вы должны адаптировать индекс и запрос время от времени.
Связанные ответы с более подробной информацией:
источник
Попробуйте классическим способом:
dbfiddle
ОБЪЯСНИТЬ АНАЛИЗ по ThreadStarter
источник