Estou com um pequeno problema no trabalho, o ORM está gerando uma consulta muito estranha e preciso otimizar um pouco o resultado dele. Eu escrevi uma versão mais legível da consulta e o resultado é semelhante, mas o tempo de execução ainda é o mesmo.
Consulta Original
Explicar Detalhado
Esta é a consulta formatada atual, ela retorna o valor correto, mas leva cerca de 13 segundos para ser executada.
select count(*)
from reservation as r
JOIN companyclient as cc ON r.companyclientid = cc.companyclientid
JOIN reservationitem as ri ON r.reservationid = ri.reservationid
LEFT JOIN billingaccount as bi
ON (r.reservationid = bi.reservationid AND cc.companyclientid = bi.companyclientid AND
bi.propertyid = '84'
AND NOT bi.isdeleted
AND bi.groupkey = bi.billingaccountid
AND bi.billingaccounttypeid = '3'
AND bi.reservationid IS NOT NULL
AND bi.statusid = '1')
WHERE r.propertyid = '84'
AND NOT r.isdeleted
AND r.companyclientid is not null
AND ri.tenantid = '025aa64f-67fb-4c23-b975-2b0fc3f5d65a'
AND NOT ri.isdeleted
AND ri.reservationitemstatusid NOT IN (6, 3, 7, 8);
Esta é a versão que escrevi na tentativa de otimizar (de 13 segundos para 5), evitando as condições dentro da junção esquerda, mas o resultado é diferente da consulta original. A primeira consulta retorna 29490 e a segunda consulta retorna 29397.
select count(*)
from reservation as r
JOIN companyclient as cc ON r.companyclientid = cc.companyclientid
JOIN reservationitem as ri ON r.reservationid = ri.reservationid
LEFT JOIN billingaccount as bi
ON (r.reservationid = bi.reservationid AND cc.companyclientid = bi.companyclientid)
WHERE r.propertyid = '84'
AND NOT r.isdeleted
AND r.companyclientid is not null
AND ri.tenantid = '025aa64f-67fb-4c23-b975-2b0fc3f5d65a'
AND NOT ri.isdeleted
AND ri.reservationitemstatusid NOT IN (6, 3, 7, 8)
AND (bi is null or bi.propertyid = '84'
AND NOT bi.isdeleted
AND bi.groupkey = bi.billingaccountid
AND bi.billingaccounttypeid = '3'
AND bi.reservationid IS NOT NULL
AND bi.statusid = '1')
Minha dúvida é, como posso otimizar a primeira consulta, já tentei alguns métodos mas não tive muito sucesso. Entendo que a contagem é linear e seu tempo é baseado no tamanho do retorno da consulta, mas imagino que seja muito lento para uma consulta de 30 mil linhas.
Neste caso específico, preciso do total de itens para calcular o número de páginas na paginação de deslocamento limite.
Desde já agradeço a todos que tiveram a paciência de ler, aceito qualquer ajuda.
Tamanho das tabelas usadas no exemplo:
- reserva: 288549 linhas
- empresacliente: 50614 linhas
- item de reserva: 387820 linhas
- conta de cobrança: 772521 linhas
Embora essa certamente seja uma consulta estranhamente escrita, usando o plano, podemos ver que há um índice ausente na conta de cobrança, sem ter que entender qual é o design da consulta geral.
Você não precisa verificar e rejeitar 7.649 linhas apenas para retornar 84, portanto, algumas das colunas no filtro devem fazer parte do índice. Não sabemos quão seletiva é cada parte do filtro, mas com base nos nomes e constantes das colunas, suponho que você precise de um índice de várias colunas sobre companyclientid e propertyid. (E provavelmente deve ter billingaccounttypeid e statusid nele também, não deve doer muito e pode ajudar)
Algumas considerações sobre a consulta. Se tivéssemos a estrutura e os índices das tabelas, seria mais útil (e o conselho talvez fosse diferente):
A 2ª consulta não é equivalente à 1ª, portanto, não é incomum que você obtenha resultados diferentes. Então, vamos nos ater à primeira consulta:
As condições
r.companyclientid is not null
ebi.reservationid IS NOT NULL
são redundantes, pois essas colunas já participam das condições de associação e qualquer valor NULL não passará nas condições de associação. Portanto, esses dois podem ser removidos com segurança (mas ainda verifique se você obtém os mesmos resultados).A
cc.companyclientid = bi.companyclientid
condição (dentro da subconsulta) pode ser substituídar.companyclientid = bi.companyclientid
desde que você tenha a condição de igualdade na consulta externa:r.companyclientid = cc.companyclientid
. Isso não deixa nenhumacc
coluna na subconsulta e todas as condições da subconsulta envolvem apenas 2 tabelas,bi
er
isso pode ajudar o planejador do Postgres a obter um plano mais simples (especialmente se houver índices adequados embillingaccount
ereservation
.Se e somente se o
reservation. companyclientid
não for anulável e houver umaFOREIGN KEY
restrição queREFERENCES companyclient (companyclientid)
, podemos remover completamente oJOIN companyclient as cc ON r.companyclientid = cc.companyclientid
desde para qualquer linha emreservation
, o estrangeiro garante que há uma única linhacompanyclient
e a contagem não seria afetada.usar
count(1)
em vez decount(*)
. Esta é apenas uma pequena melhoria em algumas versões.Se todos os itens acima forem aplicados, a consulta se tornará
(e não se esqueça de comparar os resultados com a consulta correta original após cada modificação!) :
Agora vamos sugerir alguns índices, especificamente para esta consulta e um para cada tabela:
Por favor, tente com os índices acima adicionados. Tenha em mente que qualquer índice consome espaço em disco (e espaço de memória quando usado), portanto, é uma troca.