Eu uso o PostgreSQL 9.1 no Ubuntu 12.04.
Preciso selecionar registros dentro de um intervalo de tempo: minha tabela time_limits
tem 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 levavam muito 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);
Fiquei com a impressão de que o desempenho melhorou: O tempo de acesso aos registros no meio da tabela parece ser mais razoável: algo entre 40 e 90 segundos.
Mas ainda são várias dezenas de segundos para valores no meio do intervalo de tempo. E mais duas vezes ao mirar no final da mesa (cronologicamente falando).
Tentei explain analyze
pela primeira vez obter este 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 verificando as duas colunas de carimbos de data/hora uma vez id_phi
definido como 0
. E não entendo a grande varredura (60 mil linhas!) nos carimbos 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?
Li um pouco sobre os índices GIST e GIN. Eu entendo que eles podem ser mais eficientes em certas condições para tipos personalizados. É uma opção viável para o meu caso de uso?
Para Postgres 9.1 ou posterior:
Na maioria dos casos, a ordem de classificação de um índice é pouco relevante. O Postgres pode escanear para trás praticamente com a mesma rapidez. 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. Como é verificado quanto à igualdade (=
), ele deve vir primeiro. Você acertou. Mais nesta resposta relacionada:O Postgres pode pular rapidamente
id_phi = 0
e considerar 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 de qualificação vêm primeiro. Deve ser o caminho mais rápido possível com um índice B-Tree 1 :start_date_time <= something
: o índice tem o carimbo de data/hora mais antigo primeiro.Recorra até que a primeira linha não seja qualificada (super rápido).
end_date_time >= something
: o índice tem o carimbo de data/hora mais recente primeiro.Continue com o próximo valor para a coluna 2 ..
O Postgres pode varrer para frente ou para trás. Do jeito que você tinha o índice, ele tem que 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
no manual. Se encaixa muito bem na sua pergunta.Quantas linhas correspondem nas duas primeiras colunas?
Apenas alguns com um
start_date_time
próximo ao 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 tempos 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 estimativas melhores se aumentar a meta de estatísticas da tabela. Para 2.000.000 linhas...... pode pagar. Ou ainda mais alto. 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 carimbos de data/hora (muitos valores distintos, distribuídos de forma desigual).Eu também não acho que isso importe muito com o índice aprimorado.
CLUSTER
/ pg_repack / pg_squeezeSe você quiser mais rápido, você pode simplificar a ordem física das linhas em sua tabela. Se você puder bloquear sua tabela exclusivamente (em horários de folga, por exemplo), reescreva sua tabela e ordene as linhas de acordo com o índice com
CLUSTER
:Ou considere pg_repack ou o pg_squeeze posterior , que pode fazer o mesmo sem bloqueio exclusivo na tabela.
De qualquer forma, o efeito é que menos blocos precisam ser lidos da tabela e tudo é pré-ordenado. É 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 Com a 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 de 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 as classes de operador para incluir um arquivointeger
.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 isso, citando o manual :
Ainda é verdade no Postgres 12.
Você teria que combinar um
spgist
índice em apenas(tsrange(...))
com um segundobtree
índice em(id_phi)
. Com a sobrecarga adicional, não tenho certeza se isso pode competir.Resposta relacionada com um benchmark para apenas uma
tsrange
coluna:A resposta de Erwin já é abrangente, no entanto:
Tipos de intervalo para timestamps 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 afaik). Além disso, há muitas outras razões para atualizar para o PostgreSQL 9.2.
Você pode tentar criar o índice de várias colunas em uma ordem diferente:
Eu postei uma vez uma pergunta semelhante também relacionada à ordenação de índices em um índice de várias colunas. A chave é tentar usar primeiro as condições mais restritivas para reduzir o espaço de busca.
Editado : Erro meu. Agora vejo que você já tem esse índice definido.
Consegui aumentar rapidamente (de 1 seg para 70ms)
Eu tenho uma tabela com agregações de muitas medidas e muitos níveis (
l
coluna) (30s, 1m, 1h, etc) existem duas colunas vinculadas ao intervalo:$s
para início e$e
fim.Criei dois índices de várias colunas: um para início e outro para fim.
Eu ajustei a consulta de seleção: selecione os intervalos em que o limite inicial está em um determinado intervalo. além disso, selecione intervalos em que seu limite final esteja em um determinado intervalo.
O Explain mostra dois fluxos de linhas usando nossos índices de forma eficiente.
Índices:
Selecione a consulta:
Explique:
O truque é que os nós do plano contêm apenas as linhas desejadas. Anteriormente, tínhamos milhares de linhas no nó do plano porque ele selecionou
all points from some point in time to the very end
, e o próximo nó removeu as linhas desnecessárias.