Perché il CTE è molto peggio delle sottoquery in linea


11

Sto cercando di capire meglio come funziona il planner di query in postgresql.

Ho questa domanda:

select id from users 
    where id <> 2
    and gender = (select gender from users where id = 2)
    order by latest_location::geometry <-> (select latest_location from users where id = 2) ASC
    limit 50

Funziona in meno di 10ms sul mio database con circa 500k voci nella tabella degli utenti.

Quindi ho pensato che per evitare le doppie selezioni secondarie avrei potuto riscrivere la query come CTE, in questo modo:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

Tuttavia, questa query riscritta viene eseguita in circa 1 secondo! Perché succede? Vedo che spiega che non utilizza l'indice della geometria, ma si può fare qualcosa per quello? Grazie!

Un altro modo per scrivere la query è:

select u.id, u.popularity from users u, (select gender, latest_location from users where id = 2) as me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

Tuttavia, anche questo sarà lento come il CTE.

Se invece estraggo i parametri me e li inserisco staticamente la query è di nuovo veloce:

select u.id, u.popularity from users u
    where u.gender = 'male'
    order by  u.latest_location::geometry <-> '0101000000A49DE61DA71C5A403D0AD7A370F54340'::geometry ASC
    limit 50;

Spiegare la prima query (veloce)

 Limit  (cost=5.69..20.11 rows=50 width=36) (actual time=0.512..8.114 rows=50 loops=1)
   InitPlan 1 (returns $0)
     ->  Index Scan using users_pkey on users users_1  (cost=0.42..2.64 rows=1 width=32) (actual time=0.032..0.033 rows=1 loops=1)
           Index Cond: (id = 2)
   InitPlan 2 (returns $1)
     ->  Index Scan using users_pkey on users users_2  (cost=0.42..2.64 rows=1 width=4) (actual time=0.009..0.010 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Index Scan using users_latest_location_gix on users  (cost=0.41..70796.51 rows=245470 width=36) (actual time=0.509..8.100 rows=50 loops=1)
         Order By: (latest_location <-> $0)
         Filter: (gender = $1)
         Rows Removed by Filter: 20
 Total runtime: 8.211 ms
(12 rows)

Spiegare la seconda query (lenta)

Limit  (cost=62419.82..62419.95 rows=50 width=76) (actual time=1024.963..1024.970 rows=50 loops=1)
   CTE me
     ->  Index Scan using users_pkey on users  (cost=0.42..2.64 rows=1 width=221) (actual time=0.037..0.038 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Sort  (cost=62417.18..63030.86 rows=245470 width=76) (actual time=1024.959..1024.963 rows=50 loops=1)
         Sort Key: ((u.latest_location <-> me.latest_location))
         Sort Method: top-N heapsort  Memory: 28kB
         ->  Hash Join  (cost=0.03..54262.85 rows=245470 width=76) (actual time=0.122..938.131 rows=288646 loops=1)
               Hash Cond: (u.gender = me.gender)
               ->  Seq Scan on users u  (cost=0.00..49353.41 rows=490941 width=48) (actual time=0.021..465.025 rows=490994 loops=1)
               ->  Hash  (cost=0.02..0.02 rows=1 width=36) (actual time=0.054..0.054 rows=1 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  CTE Scan on me  (cost=0.00..0.02 rows=1 width=36) (actual time=0.047..0.049 rows=1 loops=1)
 Total runtime: 1025.096 ms

3
Ne ho scritto di recente; vedi blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences . Sebbene al momento ci siano alcuni problemi DNS che potrebbero limitare la raggiungibilità di quel sito. Prova una sottoquery FROMinvece del termine CTE per ottenere i migliori risultati.
Craig Ringer,

cosa succede se usi (select id, latest_location from users where id = 2)come cte? Forse è il * che sta causando questo problema
cha

Avrei pensato che avresti cercato gli utenti più vicini di sesso opposto :)
cha

@cha Non fa alcuna differenza nella velocità per selezionare solo sesso e posizione nel cte. (Nel mio caso voglio prendere la media di utenti simili, solo che ho semplificato la query per la domanda)
viblo

@CraigRinger Non credo sia la barriera di ottimizzazione. Ho anche provato il tuo suggerimento ed è stato anche lento. D'altra parte, se estraggo manualmente i parametri è veloce (ed è una vera opzione nel mio caso, il risultato finale è comunque una funzione).
viblo,

Risposte:


11

Prova questo:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = (select gender from me)
    order by  u.latest_location::geometry <-> (select latest_location from me)::geometry ASC
    limit 50;

Quando guardo il piano veloce ecco cosa mi salta fuori (grassetto):

 Limite (costo = 5.69..20.11 righe = 50 larghezza = 36) (tempo effettivo = 0.512..8.114 righe = 50 loop = 1)
   InitPlan 1 ( restituisce $ 0 )
     -> Scansione indice utilizzando users_pkey sugli utenti users_1 (costo = 0.42..2.64 righe = 1 larghezza = 32) (tempo effettivo = 0.032..0.033 righe = 1 loop = 1)
           Indice cond: (id = 2)
   InitPlan 2 ( restituisce $ 1 )
     -> Scansione indice utilizzando users_pkey sugli utenti users_2 (costo = 0.42..2.64 righe = 1 larghezza = 4) (tempo effettivo = 0.009..0.010 righe = 1 loop = 1)
           Indice cond: (id = 2)
   -> Scansione indice utilizzando users_latest_location_gix sugli utenti (costo = 0.41..70796.51 righe = 245470 larghezza = 36) (tempo effettivo = 0.509..8.100 righe = 50 loop = 1)
         Ordina per: (latest_location   $ 0 )
         Filtro: (genere = $ 1 )
         Righe rimosse dal filtro: 20
 Durata totale: 8.211 ms
(12 file)

Nella versione lenta, il pianificatore di query sta valutando l'operatore di uguaglianza attivo gendere l'operatore di geometria attivo latest_locationnel contesto di un join , in cui il valore di mepuò variare con ogni riga (anche se ha stimato correttamente solo 1 riga). Nella versione veloce i valori di gendere latest_locationsono trattati come scalari perché sono emessi da sottoquery incorporate, il che indica al pianificatore di query che ha un solo valore di ciascuno di cui occuparsi. Questo è lo stesso motivo per cui ottieni il piano veloce quando incolli i valori letterali.


Penso che puoi rimuovere medalla fromclausola ora.
Jarius Hebzo,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.