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 um station_id
. Existem cerca de 400 station_id
valores exclusivos e cerca de 20 mil linhas por dia por dia 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) -> Classificação (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: fusão externa Disco: 681040kB -> Varredura Seq 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 é demais.
Respostas:
Para apenas 400 estações, essa consulta será massivamente mais rápida:
dbfiddle aqui
(comparando planos para esta consulta, a alternativa de Abelisto e o seu original)
Resultante
EXPLAIN ANALYZE
conforme fornecido pelo OP:O único índice que você precisa é o que você criou:
station_id__submitted_at
. AUNIQUE
restriçãouniq_sid_sat
também faz o trabalho, basicamente. Manter os dois parece um desperdício de espaço em disco e desempenho de gravação.Eu adicionei
NULLS LAST
aORDER BY
na consulta porquesubmitted_at
não está definidaNOT NULL
. Idealmente, se aplicável !, adicione umaNOT NULL
restrição à colunasubmitted_at
, descarte o índice adicional e removaNULLS LAST
da consulta.Se
submitted_at
possívelNULL
, crie esseUNIQUE
í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 a PK) - que você deve ter de qualquer maneira. Se você não tiver, crie-o. 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 estiver convencido de criá-la.Instruções detalhadas, explicações e alternativas:
Otimizar índice
Sua consulta deve ser muito rápida agora. Somente se você ainda precisar otimizar o desempenho de leitura ...
Pode fazer sentido adicionar a
level_sensor
última coluna ao índice para permitir verificações apenas de índice , como joanolo comentou .Contras: Aumenta o índice - o que adiciona um pequeno custo a todas as consultas que o utilizam.
Pro: se você realmente obtiver apenas verificações de índice, a consulta em questão não precisará visitar páginas de heap, o que o torna duas vezes mais rápido. Mas isso pode ser um ganho insubstancial para a consulta muito rápida agora.
No entanto , não espero que isso funcione para o seu caso. Você mencionou:
Normalmente, isso indicaria carga de gravação incessante (1 a
station_id
cada 5 segundos). E você está interessado na última linha. As verificações apenas de índice funcionam apenas para páginas de heap visíveis a todas as transações (o bit no mapa de visibilidade está definido). Você precisaria executarVACUUM
configurações extremamente agressivas para que a tabela acompanhasse o carregamento de gravação e ainda assim não funcionaria na maioria das vezes. Se minhas suposições estiverem corretas, as varreduras somente de índice estiverem fora, não adicionelevel_sensor
ao índice.OTOH, se minhas suposições forem válidas e sua tabela estiver crescendo muito , um índice BRIN pode ajudar. Relacionado:
Ou, ainda mais especializado e mais eficiente: um índice parcial para apenas as mais recentes adições para cortar a maior parte das linhas irrelevantes:
Escolha um carimbo de data e hora para o qual você sabe que linhas mais jovens devem existir. Você deve adicionar uma
WHERE
condição correspondente a todas as consultas, como:Você precisa adaptar o índice e a consulta periodicamente.
Respostas relacionadas com mais detalhes:
fonte
Experimente da maneira clássica:
dbfiddle
EXPLIQUE ANALISAR por ThreadStarter
fonte