Eu tenho uma tabela com 7,2 milhões de tuplas que se parece com isso:
table public.methods
column | type | attributes
--------+-----------------------+----------------------------------------------------
id | integer | not null DEFAULT nextval('methodkey'::regclass)
hash | character varying(32) | not null
string | character varying | not null
method | character varying | not null
file | character varying | not null
type | character varying | not null
Indexes:
"methods_pkey" PRIMARY KEY, btree (id)
"methodhash" btree (hash)
Agora eu quero selecionar alguns valores, mas a consulta é incrivelmente lenta:
db=# explain
select hash, string, count(method)
from methods
where hash not in
(select hash from nostring)
group by hash, string
order by count(method) desc;
QUERY PLAN
----------------------------------------------------------------------------------------
Sort (cost=160245190041.10..160245190962.07 rows=368391 width=182)
Sort Key: (count(methods.method))
-> GroupAggregate (cost=160245017241.77..160245057764.73 rows=368391 width=182)
-> Sort (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
Sort Key: methods.hash, methods.string
-> Seq Scan on methods (cost=0.00..160243305942.27 rows=3683905 width=182)
Filter: (NOT (SubPlan 1))
SubPlan 1
-> Materialize (cost=0.00..41071.54 rows=970636 width=33)
-> Seq Scan on nostring (cost=0.00..28634.36 rows=970636 width=33)
A hash
coluna é o hash md5 string
e possui um índice. Então, acho que meu problema é que toda a tabela é classificada por id e não por hash, então leva um tempo para classificá-la primeiro e depois agrupá-la?
A tabela nostring
contém apenas uma lista de hashes que eu não quero ter. Mas preciso que ambas as tabelas tenham todos os valores. Portanto, não é uma opção para excluí-los.
informações adicionais: nenhuma das colunas pode ser nula (foi corrigida na definição da tabela) e estou usando o postgresql 9.2.
NULL
valores na colunamethod
? Existem duplicatas ativadasstring
?Respostas:
A resposta do
LEFT JOIN
in @ dezso deve ser boa. Um índice, no entanto, dificilmente será útil (por si só), porque a consulta precisa ler toda a tabela de qualquer maneira - a exceção são as varreduras somente de índice no Postgres 9.2+ e condições favoráveis, veja abaixo.Execute
EXPLAIN ANALYZE
na consulta. Várias vezes para excluir efeitos de descontar e ruídos. Compare os melhores resultados.Crie um índice de várias colunas que corresponda à sua consulta:
Esperar? Depois que eu disse que um índice não ajudaria? Bem, precisamos disso para
CLUSTER
a mesa:Execute novamente
EXPLAIN ANALYZE
. Mais rápido? Deveria ser.CLUSTER
é uma operação única para reescrever a tabela inteira na ordem do índice usado. Também é efetivamente umVACUUM FULL
. Se quiser ter certeza, faça um pré-testeVACUUM FULL
sozinho para ver o que pode ser atribuído a isso.Se sua tabela vir muitas operações de gravação, o efeito será degradado com o tempo. Programe
CLUSTER
fora do horário comercial para restaurar o efeito. O ajuste fino depende do seu caso de uso exato. O manual sobreCLUSTER
.CLUSTER
é uma ferramenta bastante grosseira, precisa de um bloqueio exclusivo sobre a mesa. Se você não puder pagar, considerepg_repack
que pode fazer o mesmo sem bloqueio exclusivo. Mais nesta resposta posterior:Se a porcentagem de
NULL
valores na colunamethod
for alta (mais de ~ 20%, dependendo do tamanho real das linhas), um índice parcial deverá ajudar:(Sua atualização posterior mostra suas colunas
NOT NULL
, portanto não é aplicável.)Se você estiver executando o PostgreSQL 9.2 ou posterior (como o @deszo comentou ), os índices apresentados podem ser úteis sem
CLUSTER
que o planejador possa utilizar varreduras somente de índice . Aplicável apenas sob condições favoráveis: Nenhuma operação de gravação que afete o mapa de visibilidade desde a últimaVACUUM
e todas as colunas da consulta precisam ser cobertas pelo índice. Basicamente, as tabelas somente leitura podem usar isso a qualquer momento, enquanto as tabelas fortemente gravadas são limitadas. Mais detalhes no Wiki do Postgres.O índice parcial acima mencionado pode ser ainda mais útil nesse caso.
Se , por outro lado, não houver
NULL
valores na colunamethod
, você deve1.) defini-lo
NOT NULL
e2.) usar em
count(*)
vez decount(method)
, isso é um pouco mais rápido e faz o mesmo na ausência deNULL
valores.Se você precisar chamar essa consulta frequentemente e a tabela for somente leitura, crie a
MATERIALIZED VIEW
.Ponto fino exótico: sua tabela é nomeada
nostring
, mas parece conter hashes. Ao excluir hashes em vez de cadeias, é possível que você exclua mais cadeias do que o pretendido. Extremamente improvável, mas possível.fonte
Bem-vindo ao DBA.SE!
Você pode tentar reformular sua consulta da seguinte maneira:
ou outra possibilidade:
NOT IN
é um coletor típico de desempenho, pois é difícil usar um índice com ele.Isso pode ser aprimorado ainda mais com índices. Um índice em
nostring.hash
parece útil. Mas primeiro: o que você ganha agora? (Seria melhor ver a saída,EXPLAIN ANALYZE
pois os custos em si não informam o tempo que as operações levaram.)fonte
EXPLAIN ANALYZE
.Como o hash é um md5, você provavelmente pode tentar convertê-lo em um número: você pode armazená-lo como um número ou apenas criar um índice funcional que calcule esse número em uma função imutável.
Outras pessoas já criaram uma função pl / pgsql que converte (parte de) um valor md5 de texto em string. Consulte /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql para obter um exemplo
Acredito que você está realmente gastando muito tempo na comparação de cadeias de caracteres enquanto verifica o índice. Se você conseguir armazenar esse valor como um número, deve ser realmente muito mais rápido.
fonte
Eu me deparei muito com esse problema e descobri um truque simples em duas partes.
Crie um índice de substring no valor do hash: (7 geralmente é um bom comprimento)
create index methods_idx_hash_substring ON methods(substring(hash,1,7))
Faça com que suas pesquisas / junções incluam uma correspondência de substring, para que o planejador de consultas seja sugerido para usar o índice:
velho:
WHERE hash = :kwarg
Novo:
WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))
Você também deve ter um índice em bruto
hash
também.o resultado (geralmente) é que o planejador consultará primeiro o índice de substring e eliminará a maioria das linhas. em seguida, corresponde o hash completo de 32 caracteres ao índice (ou tabela) correspondente. essa abordagem reduziu as consultas de 800 ms para 4 para mim.
fonte