Eu tenho uma tabela station_logs
em um banco de dados 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)
Estou tentando obter o último level_sensor
valor com base em submitted_at
, para cada station_id
. Existem cerca de 400 station_id
valores exclusivos e cerca de 20 mil linhas por dia por station_id
.
Antes de criar o índice:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Exclusivo (custo=4347852.14..4450301.72 linhas=89 largura=20) (tempo real=22202.080..27619.167 linhas=98 loops=1) -> Classificar (custo=4347852.14..4399076.93 linhas=20489916 largura=20) (tempo real=22202.077..26540.827 linhas=20489812 loops=1) Chave de classificação: station_id, submit_at DESC Método de classificação: mesclagem externa Disco: 681040kB -> Seq Scan em station_logs (custo=0.00..598895.16 linhas=20489916 largura=20) (tempo real=0.023..3443.587 linhas=20489812 loops=$ Tempo de planejamento: 0,072 ms Tempo de execução: 27690,644 ms
Criando índice:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
Após criar o índice, para a mesma consulta:
Exclusivo (custo=0,56..2156367,51 linhas=89 largura=20) (tempo real=0,184..16263.413 linhas=98 loops=1) -> Varredura de índice usando station_id__submitted_at em station_logs (custo=0.56..2105142.98 linhas=20489812 largura=20) (tempo real=0.181..1$ Tempo de planejamento: 0,206 ms Tempo de execução: 16263,490 ms
Existe uma maneira de tornar essa consulta mais rápida? Como 1 segundo, por exemplo, 16 segundos ainda é muito.
Para apenas 400 estações, essa consulta será muito mais rápida:
dbfiddle aqui (comparando planos para esta consulta, alternativa de Abelisto e seu original)
Resultando
EXPLAIN ANALYZE
conforme fornecido pelo OP:O único índice que você precisa é aquele que você criou:
station_id__submitted_at
. AUNIQUE
restriçãouniq_sid_sat
também faz o trabalho, basicamente. Manter ambos parece um desperdício de espaço em disco e desempenho de gravação.Eu adicionei
NULLS LAST
naORDER BY
consulta porquesubmitted_at
não está definidoNOT NULL
. Idealmente, se aplicável!, adicione umaNOT NULL
restrição à colunasubmitted_at
, elimine o índice adicional e removaNULLS LAST
da consulta.Se
submitted_at
puder serNULL
, crie esteUNIQUE
índice para substituir o índice atual e a restrição exclusiva:Considerar:
Isso pressupõe uma tabela separada
station
com uma linha por relevantestation_id
(normalmente o PK) - que você deve ter de qualquer maneira. Se você não tem, crie. Novamente, muito rápido com esta técnica rCTE:Eu uso isso no violino também. Você pode usar uma consulta semelhante para resolver sua tarefa diretamente, sem
station
tabela - se não puder ser convencido a criá-la.Instruções detalhadas, explicações e alternativas:
Índice de otimização
Sua consulta deve ser muito rápida agora. Somente se você ainda precisar otimizar o desempenho de leitura ...
Pode fazer sentido adicionar
level_sensor
como última coluna ao índice para permitir varreduras somente de índice , como joanolo commented .Contra: Torna o índice maior - o que adiciona um pequeno custo a todas as consultas que o utilizam.
Pro: Se você realmente obtiver apenas varreduras de índice, a consulta em questão não precisará visitar as páginas de heap, o que a torna duas vezes mais rápida. Mas isso pode ser um ganho insubstancial para a consulta muito rápida agora.
No entanto , não espero que funcione para o seu caso. Você mencionou:
Normalmente, isso indicaria uma carga de gravação incessante (1 a
station_id
cada 5 segundos). E você está interessado na última linha. As varreduras somente de índice funcionam apenas para páginas de heap visíveis para todas as transações (o bit no mapa de visibilidade está definido). Você teria que executarVACUUM
configurações extremamente agressivas para a tabela acompanhar a carga de gravação e ainda não funcionaria na maioria das vezes. Se minhas suposições estiverem corretas, as varreduras somente de índice estão fora, não adicionelevel_sensor
ao índice.OTOH, se minhas suposições se confirmarem e sua tabela estiver crescendo muito , um índice BRIN pode ajudar. Relacionado:
Ou ainda mais especializado e mais eficiente: um índice parcial apenas para as últimas adições para cortar a maior parte das linhas irrelevantes:
Escolha um carimbo de data/hora para o qual você sabe que as linhas mais recentes devem existir. Você precisa adicionar uma
WHERE
condição de correspondência a todas as consultas, como:Você precisa adaptar o índice e a consulta de tempos em tempos.
Respostas relacionadas com mais detalhes:
Experimente a forma clássica:
dbfiddle
EXPLIQUE ANALISAR por ThreadStarter