Re: Hash Join over Nested Loop

Luís Roberto Weck <[email protected]> Fri, 22 Nov 2019 15:48:22 -0300
Newsgroups gmane.comp.db.postgresql.performance
Message-ID <[email protected]>
****
Em 22/11/2019 14:55, Pavel Stehule escreveu:
>
>
> pá 22. 11. 2019 v 18:37 odesílatel Luís Roberto Weck 
> <[email protected] <mailto:[email protected]>> napsal:
>
>>     Hey,
>>
>>     I'm trying to figure out why Postgres is choosing a Hash Join
>>     over a Nested Loop in this query:
>>
>>     SELECT T1.PesID, T1.PesNom, T1.PesValSal, T1.PesCPFCNPJ,
>>     T2.CarAti, T1.CarCod, T1.EmpCod,
>>            T2.CarFan, T1.PesDatAge, T1.PesCod, COALESCE(
>>     T3.PesDatAnt, DATE '00010101') AS PesDatAnt
>>       FROM ((public.Pessoa T1
>>                 INNER JOIN public.Carteira T2 ON T2.EmpCod =
>>     T1.EmpCod AND T2.CarCod = T1.CarCod)
>>                  LEFT JOIN  (SELECT MIN(COALESCE( T5.ConVenAnt, DATE
>>     '00010101')) AS PesDatAnt, T4.EmpCod, T4.CarCod, T4.ConPesCod AS
>>     ConPesCod
>>                                FROM (public.Contrato T4
>>                                      LEFT JOIN (SELECT
>>     MIN(ConParDatVen) AS ConVenAnt, EmpCod, CarCod, ConPesCod, ConSeq
>>     FROM public.ContratoParcela T5
>>     WHERE ConParAti = true
>>     AND ConParValSal > 0
>>     GROUP BY EmpCod, CarCod, ConPesCod, ConSeq ) T5 ON T5.EmpCod    =
>>     T4.EmpCod    AND
>>     T5.CarCod    = T4.CarCod    AND
>>     T5.ConPesCod = T4.ConPesCod AND
>>     T5.ConSeq    = T4.ConSeq)
>>                               WHERE T4.ConAti = TRUE
>>     GROUP BY T4.EmpCod, T4.CarCod, T4.ConPesCod ) T3 ON t3.EmpCod   
>>     = T1.EmpCod AND
>>                               t3.CarCod    = T1.CarCod AND
>>                               t3.ConPesCod = T1.PesCod)
>>      WHERE (T2.CarAti = true)
>>        AND (T1.EmpCod = 112)
>>        and (UPPER(T1.PesNom) like UPPER('%MARIA%'))
>>      ORDER BY T1.EmpCod, T1.CarCod, T1.PesCod
>>
>>     Here the Hash Join[1] plan takes ~700ms, and if I change the
>>     first LEFT JOIN to a LEFT JOIN LATERAL, forcing a nested loop,
>>     the query[2] runs in 3ms.
>>
>>     [1] https://explain.depesz.com/s/8IL3
>>     [2] https://explain.depesz.com/s/f8Q9
>
>
> Maybe I am wrong, but probably you have to do more than just change 
> LEFT JOIN to LATERAL JOIN. Lateral join is based on correlated 
> subquery - so you had to push some predicates to subquery - and then 
> the query can be much more effective.
>
> Regards
>
> Pavel
>
>
>
>
>
>     PostgreSQL version is 11.5, I have run analyze on all the tables.
>
>     PG settings:
>
>     name                           |setting  |unit|
>     -------------------------------|---------|----|
>     autovacuum                     |on       |    |
>     default_statistics_target      |250      |    |
>     effective_cache_size           |983040   |8kB |
>     effective_io_concurrency       |200      |    |
>     max_parallel_workers           |6        |    |
>     max_parallel_workers_per_gather|3        |    |
>     random_page_cost               |1.1      |    |
>     work_mem                       |51200    |kB  |
>

I'm sorry, I am not sure I understood.

This is the altered query:

SELECT T1.PesID, T1.PesNom, T1.PesValSal, T1.PesCPFCNPJ, T2.CarAti, 
T1.CarCod, T1.EmpCod, T2.CarFan, T1.PesDatAge, T1.PesCod,
        COALESCE( T3.PesDatAnt, DATE '00010101') AS PesDatAnt
   FROM ((public.Pessoa T1
             INNER JOIN public.Carteira T2 ON T2.EmpCod = T1.EmpCod AND 
T2.CarCod = T1.CarCod)
              LEFT JOIN *LATERAL *(SELECT MIN(COALESCE( T5.ConVenAnt, 
DATE '00010101')) AS PesDatAnt, T4.EmpCod, T4.CarCod, T4.ConPesCod AS 
ConPesCod
                                   FROM (public.Contrato T4
                                         LEFT JOIN  (SELECT 
MIN(ConParDatVen) AS ConVenAnt, EmpCod, CarCod, ConPesCod, ConSeq
                                                      FROM 
public.ContratoParcela T5
                                                     WHERE ConParAti = true
                                                       and ConParValSal > 0

                                                     GROUP BY EmpCod, 
CarCod, ConPesCod, ConSeq ) T5 ON  T5.EmpCod = T4.EmpCod AND T5.CarCod = 
T4.CarCod AND T5.ConPesCod = T4.ConPesCod AND T5.ConSeq = T4.ConSeq)
                                  WHERE T4.ConAti = TRUE
*AND t4.EmpCod = T1.EmpCod AND t4.CarCod = T1.CarCod AND t4.ConPesCod = 
T1.PesCod*
                                  GROUP BY T4.EmpCod, T4.CarCod, 
T4.ConPesCod ) T3 ON *TRUE ) --ON t3.EmpCod = T1.EmpCod AND t3.CarCod = 
T1.CarCod AND t3.ConPesCod = T1.PesCod) *
  WHERE (T2.CarAti = true)
    AND (T1.EmpCod = 112)
    and (UPPER(T1.PesNom) like UPPER('%MARIA%'))
  ORDER BY T1.EmpCod, T1.CarCod, T1.PesCod

In bold are the changes I've made to the query. I am sure PostgreSQL is 
able to push it down, since it is much faster now.The problem I have is 
that this is a query generated by an ORM, So I can't change it.

I would like to understand why wasn't Postgres able to optimize it to a 
nested loop. Is there something I can do with the statistics?

Thanks!!