Eu uso o PostgreSQL 9.1 no Ubuntu 12.04.
Preciso selecionar registros dentro de um intervalo de tempo: minha tabela time_limits
possui dois timestamp
campos e uma integer
propriedade. Existem colunas adicionais na minha tabela real que não estão envolvidas com esta consulta.
create table (
start_date_time timestamp,
end_date_time timestamp,
id_phi integer,
primary key(start_date_time, end_date_time,id_phi);
Esta tabela contém aproximadamente 2 milhões de registros.
Consultas como as seguintes levaram uma quantidade enorme de tempo:
select * from time_limits as t
where t.id_phi=0
and t.start_date_time <= timestamp'2010-08-08 00:00:00'
and t.end_date_time >= timestamp'2010-08-08 00:05:00';
Então, tentei adicionar outro índice - o inverso do PK:
create index idx_inversed on time_limits(id_phi, start_date_time, end_date_time);
Tive a impressão de que o desempenho melhorou: o tempo para acessar registros no meio da tabela parece ser mais razoável: algo entre 40 e 90 segundos.
Mas ainda há várias dezenas de segundos para valores no meio do intervalo de tempo. E mais duas vezes ao mirar o final da tabela (cronologicamente falando).
Tentei explain analyze
pela primeira vez obter esse plano de consulta:
Bitmap Heap Scan on time_limits (cost=4730.38..22465.32 rows=62682 width=36) (actual time=44.446..44.446 rows=0 loops=1)
Recheck Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
-> Bitmap Index Scan on idx_time_limits_phi_start_end (cost=0.00..4714.71 rows=62682 width=0) (actual time=44.437..44.437 rows=0 loops=1)
Index Cond: ((id_phi = 0) AND (start_date_time <= '2011-08-08 00:00:00'::timestamp without time zone) AND (end_date_time >= '2011-08-08 00:05:00'::timestamp without time zone))
Total runtime: 44.507 ms
Veja os resultados em depesz.com.
O que eu poderia fazer para otimizar a pesquisa? Você pode ver todo o tempo gasto varrendo as duas colunas de carimbo de data / hora, uma vez id_phi
definido como 0
. E eu não entendo a grande varredura (60 mil linhas!) Nos registros de data e hora. Eles não são indexados pela chave primária e idx_inversed
eu adicionei?
Devo mudar de tipos de carimbo de data / hora para outra coisa?
Eu li um pouco sobre os índices GIST e GIN. Acho que eles podem ser mais eficientes em determinadas condições para tipos personalizados. É uma opção viável para o meu caso de uso?
fonte
explain analyze
saída é o tempo que a consulta precisava no servidor . Se a sua consulta demorar 45 segundos, o tempo adicional será gasto transferindo os dados do banco de dados para o programa que está executando a consulta. Afinal, são 62682 linhas e se cada linha é grande (por exemplo, possui longasvarchar
outext
colunas), isso pode afetar o tempo de transferência. drasticamente.rows=62682 rows
é a estimativa do planejador . A consulta retorna 0 linhas.(actual time=44.446..44.446 rows=0 loops=1)
Respostas:
Para o Postgres 9.1 ou posterior:
Na maioria dos casos, a ordem de classificação de um índice é pouco relevante. O Postgres pode retroceder praticamente o mais rápido possível. Mas, para consultas de intervalo em várias colunas, isso pode fazer uma enorme diferença. Intimamente relacionado:
Considere sua consulta:
A ordem de classificação da primeira coluna
id_phi
no índice é irrelevante. Uma vez que foi verificado para igualdade (=
), ele deve vir primeiro. Você acertou. Mais nesta resposta relacionada:O Postgres pode pular rapidamente
id_phi = 0
e considere as duas colunas a seguir do índice correspondente. Eles são consultados com condições de intervalo de ordem de classificação invertida (<=
,>=
). No meu índice, as linhas qualificadas são as primeiras. Deve ser a maneira mais rápida possível com o índice 1 da Árvore B :start_date_time <= something
: o índice tem o carimbo de data e hora mais antigo primeiro.Continue até a primeira linha não se qualificar (super rápido).
end_date_time >= something
: o índice possui o carimbo de data e hora mais recente primeiro.Continue com o próximo valor para a coluna 2.
O Postgres pode avançar ou retroceder. Da maneira que você teve o índice, ele precisa ler todas as linhas correspondentes nas duas primeiras colunas e depois filtrar na terceira. Certifique-se de ler o capítulo Índices e
ORDER BY
o manual. Ele se encaixa muito bem na sua pergunta.Quantas linhas correspondem nas duas primeiras colunas?
Apenas alguns estão
start_date_time
perto do início do intervalo de tempo da tabela. Mas quase todas as linhas estãoid_phi = 0
no final cronológico da tabela! Portanto, o desempenho se deteriora com os horários de início posteriores.Estimativas do planejador
O planejador estima
rows=62682
para sua consulta de exemplo. Desses, nenhum se qualifica (rows=0
). Você pode obter melhores estimativas se aumentar o destino das estatísticas da tabela. Para 2.000.000 linhas ...... pode pagar. Ou ainda mais. Mais nesta resposta relacionada:
Eu acho que você não precisa disso para
id_phi
(apenas alguns valores distintos, distribuídos uniformemente), mas para os registros de data e hora (muitos valores distintos, distribuídos de maneira desigual).Também não acho que isso importe muito com o índice aprimorado.
CLUSTER
/ pg_repackSe você quiser mais rápido, ainda assim, poderá otimizar a ordem física das linhas na sua tabela. Se você pode bloquear sua tabela exclusivamente por um curto período de tempo (por exemplo, fora do horário de expediente) para reescrever sua tabela e ordenar linhas de acordo com o índice:
Com acesso simultâneo, considere pg_repack , que pode fazer o mesmo sem bloqueio exclusivo.
De qualquer forma, o efeito é que menos blocos precisam ser lidos da tabela e tudo é pré-classificado. É um efeito único que se deteriora com o tempo, com gravações na tabela fragmentando a ordem de classificação física.
Índice GiST no Postgres 9.2+
1 Na página 9.2+, existe outra opção possivelmente mais rápida: um índice GiST para uma coluna de intervalo.
Existem tipos de intervalo integrados para
timestamp
etimestamp with time zone
:tsrange
,tstzrange
. Um índice btree normalmente é mais rápido para umainteger
coluna adicional comoid_phi
. Menor e mais barato de manter também. Mas a consulta provavelmente ainda será mais rápida no geral com o índice combinado.Altere sua definição de tabela ou use um índice de expressão .
Para o índice GiST com várias colunas em mãos, você também precisa do módulo adicional
btree_gist
instalado (uma vez por banco de dados), que fornece às classes de operadores para incluir uminteger
.O trio! Um índice GiST funcional de várias colunas :
Use o operador "contém intervalo"
@>
em sua consulta agora:Índice SP-GiST no Postgres 9.3+
Um índice SP-GiST pode ser ainda mais rápido para esse tipo de consulta - exceto que, citando o manual :
Ainda é verdade no Postgres 12.
Você teria que combinar um
spgist
índice apenas(tsrange(...))
com um segundobtree
índice(id_phi)
. Com a sobrecarga adicional, não tenho certeza se isso pode competir.Resposta relacionada com uma referência para apenas uma
tsrange
coluna:fonte
A resposta de Erwin já é abrangente, no entanto:
Os tipos de intervalo para registros de data e hora estão disponíveis no PostgreSQL 9.1 com a extensão Temporal de Jeff Davis: https://github.com/jeff-davis/PostgreSQL-Temporal
Nota: possui recursos limitados (usa Timestamptz, e você só pode ter o estilo '[)' sobreposto). Além disso, existem muitas outras ótimas razões para atualizar para o PostgreSQL 9.2.
fonte
Você pode tentar criar o índice de várias colunas em uma ordem diferente:
Postei uma vez uma pergunta semelhante também relacionada à ordenação de índices em um índice de várias colunas. A chave está tentando usar primeiro as condições mais restritivas para reduzir o espaço de pesquisa.
Edit : Meu erro. Agora vejo que você já tem esse índice definido.
fonte
Bitmap Index Scan on idx_time_limits_phi_start_end
Consegui aumentar rapidamente (de 1 segundo para 70ms)
Eu tenho uma tabela com agregações de muitas medições e muitos níveis (
l
coluna) (30s, 1m, 1h, etc), existem duas colunas ligadas ao intervalo:$s
para início e$e
fim.Criei dois índices de várias colunas: um para o início e outro para o fim.
Eu ajustei a consulta de seleção: selecione os intervalos em que o limite inicial é determinado. Além disso, selecione intervalos em que o limite final esteja em determinado intervalo.
O Explain mostra dois fluxos de linhas usando nossos índices com eficiência.
Índices:
Selecionar consulta:
Explicar:
O truque é que os nós do plano contêm apenas linhas desejadas. Anteriormente, obtivemos milhares de linhas no nó do plano porque ele foi selecionado e
all points from some point in time to the very end
, em seguida, o próximo nó removeu as linhas desnecessárias.fonte