Eu tenho uma consulta relativamente simples em uma tabela com linhas de 1,5 milhão:
SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
resultado:
Limit (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1) -> Bitmap Heap Scan on publication (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1) Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321)) -> BitmapOr (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1) -> Bitmap Index Scan on publication_pkey (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1) Index Cond: (mtid = 9762715) -> Bitmap Index Scan on publication_last_modifier_btree (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1) Index Cond: (last_modifier = 21321) Total runtime: 1.027 ms
Até aí tudo bem, rápido e usa os índices disponíveis.
Agora, se eu modificar uma consulta um pouco, o resultado será:
SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
A EXPLAIN ANALYZE
saída é:
Limit (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1) -> Seq Scan on publication (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1) Filter: ((hashed SubPlan 1) OR (last_modifier = 21321)) SubPlan 1 -> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1) Total runtime: 2841.442 ms
Não é tão rápido, e usando o seq scan ...
Obviamente, a consulta original executada pelo aplicativo é um pouco mais complexa e ainda mais lenta, e é claro que o original gerado por hibernação não é (SELECT 9762715)
, mas a lentidão existe mesmo para isso (SELECT 9762715)
! A consulta é gerada pelo hibernate, por isso é um grande desafio alterá-los e alguns recursos não estão disponíveis (por exemplo, UNION
não estão disponíveis, o que seria rápido).
As questões
- Por que o índice não pode ser usado no segundo caso? Como eles poderiam ser usados?
- Posso melhorar o desempenho da consulta de outra maneira?
Pensamentos adicionais
Parece que poderíamos usar o primeiro caso executando manualmente um SELECT e colocando a lista resultante na consulta. Mesmo com 5000 números na lista IN (), é quatro vezes mais rápido que a segunda solução. No entanto, parece errado (também, pode ser 100 vezes mais rápido :)). É completamente incompreensível o motivo pelo qual o planejador de consultas usa um método completamente diferente para essas duas consultas. Por isso, gostaria de encontrar uma solução melhor para esse problema.
JOIN
vez deIN ()
? Além disso,publication
foi analisado recentemente?(SELECT 9762715)
.(SELECT 9762715)
. Para a pergunta de hibernação: isso pode ser feito, mas requer uma reescrita séria de código, pois temos consultas de critérios de hibernação definidas pelo usuário que são traduzidas on-the-fly. Então, basicamente estaríamos modificando o hibernate, o que é uma tarefa enorme, com muitos efeitos colaterais possíveis.Respostas:
O núcleo do problema se torna óbvio aqui:
O Postgres estima retornar 744661 linhas, enquanto, na verdade, acaba sendo uma única linha. Se o Postgres não souber melhor o que esperar da consulta, ele não poderá planejar melhor. Precisamos ver a consulta real oculta por trás
(SELECT 9762715)
- e provavelmente também conhecer a definição da tabela, restrições, cardinalidades e distribuição de dados. Obviamente, o Postgres não é capaz de prever quantas poucas linhas serão retornadas por ele. Pode haver maneiras de reescrever a consulta, dependendo do que é .Se você sabe que a subconsulta nunca pode retornar mais do que
n
linhas, basta informar ao Postgres usando:Se n for pequeno o suficiente, o Postgres mudará para verificações de índice (bitmap). No entanto , isso só funciona para o caso simples. Para de trabalhar ao adicionar uma
OR
condição: o planejador de consultas não pode lidar com isso no momento.Eu raramente uso
IN (SELECT ...)
para começar. Normalmente, há uma maneira melhor de implementar o mesmo, geralmente com umaEXISTS
semi-junção. Às vezes com um (LEFT
)JOIN
(LATERAL
) ...A solução óbvia seria usar
UNION
, mas você descartou isso. Não posso dizer mais sem conhecer a subconsulta real e outros detalhes relevantes.fonte
(SELECT 9762715)
! Se eu executar essa consulta exata que você vê acima. Obviamente, a consulta de hibernação original é um pouco mais complicada, mas (acho que) consegui identificar onde o planejador de consultas se desvia, então apresentei essa parte da consulta. No entanto, as explicações e consultas acima são verbatim ctrl-cv.EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;
também faz uma varredura seqüencial e também é executada por cerca de 3 segundos ...CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;
. E o efeito ainda estava lá para as mesmas consultastest
: qualquer subconsulta resultou em uma verificação seq ... Eu tentei as versões 9.1 e 9.4. O efeito é o mesmo.OR
condição. O truqueLIMIT
só funciona para o caso mais simples.Meu colega encontrou uma maneira de alterar a consulta para que ele precise de uma reescrita simples e faça o que precisa, ou seja, fazer a subseleção em uma etapa e depois realizar as operações adicionais no resultado:
A explicação analisar agora é:
Parece que podemos criar um analisador simples que encontre e reescreva todos os subselecionados dessa maneira e adicione-o a um gancho de hibernação para manipular a consulta nativa.
fonte
SELECT
s, como você fez na sua primeira consulta na pergunta?SELECT
separadamente e, em seguida, fazer a seleção externa com uma lista estática após oIN
. No entanto, isso é significativamente mais lento (de 5 a 10 vezes, se a subconsulta tiver mais do que alguns resultados), pois você possui viagens de ida e volta extras à rede, além de que o postgres formata muitos resultados e, em seguida, java analisa esses resultados (e executa o mesmo novamente para trás). A solução acima faz o mesmo semântica, deixando o processo dentro do postgres. Em suma, atualmente, este parece ser o caminho mais rápido, com a menor modificação no nosso caso.Responda a uma segunda pergunta: Sim, você pode adicionar ORDER BY à sua subconsulta, o que terá um impacto positivo. Mas é semelhante à solução "EXISTS (subconsulta)" no desempenho. Há uma diferença significativa mesmo com a subconsulta resultando em duas linhas.
fonte