Лише 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)
-> Сканування послідовності на станціях 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)
-> Сканування покажчика за допомогою станції_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
(зазвичай ПК) - який у вас повинен бути будь-який спосіб. Якщо у вас його немає, створіть його. Знову ж таки, дуже швидко з цією технікою 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: Він збільшує індекс - це додає невеликих витрат на всі запити, що використовують його.
Про: Якщо ви фактично отримуєте з нього лише скани з індексом, запит під рукою зовсім не повинен відвідувати купі сторінки, що робить його приблизно вдвічі швидшим. Але це може бути суттєвим виграшем для дуже швидкого запиту зараз.
Однак я не очікую, що це спрацює у вашій справі. Ви згадали:
... близько 20 тис. рядків на день station_id
.
Як правило, це свідчить про невпинне завантаження запису (1 на station_id
кожні 5 секунд). І вас цікавить останній ряд. Сканування, призначене лише для покажчиків, працює лише для купи сторінок, видимих для всіх транзакцій (встановлено біт на карті видимості). Вам слід запустити надзвичайно агресивні VACUUM
настройки для таблиці, щоб не відставати від завантаження запису, і це все ще не працюватиме більшу частину часу. Якщо мої припущення є правильними, сканування, призначене лише для індексу, немає, не додайте level_sensor
його до індексу.
ОТОХ, якщо мої припущення виконуються, а ваша таблиця зростає дуже великою , індекс 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'
...
Вам доведеться час від часу адаптувати індекс і запит.
Відповідні відповіді з більш детальною інформацією: