6

mi viene data questa query per ottimizzare su PostgreSQL 9.2:ottimizzazione delle query PostgreSQL No interno/esterno join permesso

SELECT C.name, COUNT(DISTINCT I.id) AS NumItems, COUNT(B.id) 
FROM Categories C INNER JOIN Items I ON(C.id = I.category) 
        INNER JOIN Bids B ON (I.id = B.item_id) 
GROUP BY C.name 

Come parte del mio incarico scuola.

ho creato questi indici su rispettivi tavolo: items(category) -> 2ndary b + tree, bids(item_id) -> 2ndary albero B +, e categories(id) -> indice primario qui,

La parte strana è, PostgreSQL sta facendo una scansione sequenziale sulla mia tabella Articoli, Categorie e Offerte e quando imposto lo enable_seqscan=off, la ricerca dell'indice risulta essere più orrenda del risultato sottostante.

Quando corro spiegare in PostgreSQL questo è il risultato: PER FAVORE NON RIMUOVERE LE INDICAZIONI IN QUANTO SONO IMPORTANTI!

GroupAggregate (cost=119575.55..125576.11 rows=20 width=23) (actual time=6912.523..9459.431 rows=20 loops=1) 
    Buffers: shared hit=30 read=12306, temp read=6600 written=6598 
    -> Sort (cost=119575.55..121075.64 rows=600036 width=23) (actual time=6817.015..8031.285 rows=600036 loops=1) 
     Sort Key: c.name 
     Sort Method: external merge Disk: 20160kB 
     Buffers: shared hit=30 read=12306, temp read=6274 written=6272 
     -> Hash Join (cost=9416.95..37376.03 rows=600036 width=23) (actual time=407.974..3322.253 rows=600036 loops=1) 
       Hash Cond: (b.item_id = i.id) 
       Buffers: shared hit=30 read=12306, temp read=994 written=992 
       -> Seq Scan on bids b (cost=0.00..11001.36 rows=600036 width=8) (actual time=0.009..870.898 rows=600036 loops=1) 
        Buffers: shared hit=2 read=4999 
       -> Hash (cost=8522.95..8522.95 rows=50000 width=19) (actual time=407.784..407.784 rows=50000 loops=1) 
        Buckets: 4096 Batches: 2 Memory Usage: 989kB 
        Buffers: shared hit=28 read=7307, temp written=111 
        -> Hash Join (cost=1.45..8522.95 rows=50000 width=19) (actual time=0.082..313.211 rows=50000 loops=1) 
          Hash Cond: (i.category = c.id) 
          Buffers: shared hit=28 read=7307 
          -> Seq Scan on items i (cost=0.00..7834.00 rows=50000 width=8) (actual time=0.004..144.554 rows=50000 loops=1) 
           Buffers: shared hit=27 read=7307 
          -> Hash (cost=1.20..1.20 rows=20 width=19) (actual time=0.062..0.062 rows=20 loops=1) 
           Buckets: 1024 Batches: 1 Memory Usage: 1kB 
           Buffers: shared hit=1 
           -> Seq Scan on categories c (cost=0.00..1.20 rows=20 width=19) (actual time=0.004..0.028 rows=20 loops=1) 
             Buffers: shared hit=1 
Total runtime: 9473.257 ms 

Vedere this plan on explain.depesz.com.

Voglio solo sapere perché questo accade, ad esempio perché gli indici rendono la query orrendamente lenta rispetto alla scansione sequenziale.

Modifica: Penso di essere riuscito a scoprire un paio di cose passando attraverso la documentazione postgresql. Postgresql ha deciso di eseguire la scansione seq su alcune tabelle come offerte e articoli in quanto prevedeva che doveva recuperare ogni singola riga nella tabella (confrontare il numero di righe nella parentesi prima del tempo effettivo e il numero di righe nel tempo effettivo parte). La scansione sequenziale è migliore nel recupero di tutte le righe. Beh, nulla può essere fatto in quella parte.

Ho creato un indice aggiuntivo per categories(name) e il risultato di seguito è quello che ho. In qualche modo è migliorato, ma ora l'hash join viene sostituito con il ciclo annidato. Qualche indizio sul perché?

GroupAggregate (cost=0.00..119552.02 rows=20 width=23) (actual time=617.330..7725.314 rows=20 loops=1) 
    Buffers: shared hit=178582 read=37473 written=14, temp read=2435 written=436 
    -> Nested Loop (cost=0.00..115051.55 rows=600036 width=23) (actual time=0.120..6186.496 rows=600036 loops=1) 
     Buffers: shared hit=178582 read=37473 written=14, temp read=2109 written=110 
     -> Nested Loop (cost=0.00..26891.55 rows=50000 width=19) (actual time=0.066..2827.955 rows=50000 loops=1) 
       Join Filter: (c.id = i.category) 
       Rows Removed by Join Filter: 950000 
       Buffers: shared hit=2 read=7334 written=1, temp read=2109 written=110 
       -> Index Scan using categories_name_idx on categories c (cost=0.00..12.55 rows=20 width=19) (actual time=0.039..0.146 rows=20 loops=1) 
        Buffers: shared hit=1 read=1 
       -> Materialize (cost=0.00..8280.00 rows=50000 width=8) (actual time=0.014..76.908 rows=50000 loops=20) 
        Buffers: shared hit=1 read=7333 written=1, temp read=2109 written=110 
        -> Seq Scan on items i (cost=0.00..7834.00 rows=50000 width=8) (actual time=0.007..170.464 rows=50000 loops=1) 
          Buffers: shared hit=1 read=7333 written=1 
     -> Index Scan using bid_itemid_idx on bids b (cost=0.00..1.60 rows=16 width=8) (actual time=0.016..0.036 rows=12 loops=50000) 
       Index Cond: (item_id = i.id) 
       Buffers: shared hit=178580 read=30139 written=13 
Total runtime: 7726.392 ms 

Date un'occhiata sul piano here se è meglio.

Sono riuscito a ridurlo a 114062.92 creando indice su categoria (id) e items(category). Postgresql ha utilizzato entrambi gli indici per ottenere il costo di 114062,92. Tuttavia, ora postgresql sta giocando con me non usando l'indice! perché è così buggy?

+0

Per raggruppare per nome, è necessario ordinare per nome, anche se se metti un indice su categories.name potrebbe funzionare meglio. Metterei anche un indice su items.id per cercare di accelerare il distinto (i.id) e il join su b.item_id. –

+0

Inoltre, tieni presente che Postgres "spiega il piano" potrebbe non darti buoni risultati fino a dopo aver fatto un "analisi del vuoto". –

+0

grazie per la risposta @PaulTomblin. Yeap mi sono assicurato di aver usato l'analisi del vuoto prima di spiegare il piano per ottenere un risultato accurato. articoli.id è la chiave primaria per la tabella degli articoli, quindi conterrà l'indice cluster. – ImNoob

risposta

1

Grazie per aver inviato l'output EXPLAIN senza chiedere, e per il EXPLAIN (BUFFERS, ANALYZE).

Una parte significativa del problema di prestazioni di query è probabile che sia il nodo di piano di tipo esterno, che sta facendo un merge sort su disco con un file temporaneo:

Sort Method: external merge Disk: 20160kB 

Si potrebbe fare questo genere in memoria impostando:

SET work_mem = '50MB'; 

prima di eseguire la query. Questa impostazione può anche essere impostata per utente, per database o globalmente in postgresql.conf.

Non sono convinto che l'aggiunta di indici sarà di grande utilità in quanto la query è attualmente strutturata. Ha bisogno di leggere e unire tutte le righe di tutte e tre le tabelle e gli hash join potrebbero essere il modo più veloce per farlo.

sospetto che ci sono altri modi per esprimere che query che utilizzerà strategie di esecuzione completamente diverso e più efficiente, ma sto avendo un cervello-dissolvenza su quello potrebbero essere e non vogliono passare il tempo creare tavoli fittizi per giocare. Altro work_mem dovrebbe migliorare significativamente la query così com'è.

+0

grazie per l'aiuto. Certamente quel set work_mem migliora notevolmente le prestazioni. Sfortunatamente non mi è permesso usarlo nella mia risposta. Ho aggiunto indici per tutti gli attributi coinvolti e l'unico che sembra essere utile è l'indice sulle categorie (nome) per rendere il gruppo più veloce. A parte questo, nessun altro indice lo rende migliore. Pubblicherò qualsiasi aggiornamento se riesco ad aggiornarlo meglio. – ImNoob

+0

Esiste una differenza tra gli indici e i vincoli di chiave esterna. L'OP ha bisogno di vincoli FK, non di indici. Inoltre: come ha detto @Craig Ringer: la query richiede * tutte * le righe (da tutte e 3 le tabelle per il vincolo FK, o (forse) meno quando si usano gli indici e le statistiche corrette. L'ordinamento/gruppo finale per è un killer, poiché è ortogonale alle altre dimensioni chiave – wildplasser

+0

@ImNoob Se pubblichi uno schema e alcuni dati fittizi su sqlfiddle.com è più facile sperimentare approcci completamente diversi alla query: –

0

Da piani di query possiamo vedere che:
1. Risultato e categorie hanno 20 record
2. I prodotti con categoria sono il 5% di tutte le quantità di prodotti che
"Righe rimosso con filtro join: 950000"
"rows = 50000" in scansione sequenziale
3. Le offerte corrispondenti sono righe = 600036 (potresti darci il numero totale di offerte?)
4. Ogni categoria ha un'offerta?

Quindi vogliamo utilizzare indici su articoli (categoria) e offerte (item_id). Vogliamo anche ordinare in memoria.

select 
    (select name from Categories where id = foo.category) as name, 
    count(foo.id), 
    sum(foo.bids_count) 
from 
    (select 
     id, 
     category, 
     (select count(item_id) from Bids where item_id = i.id) as bids_count 
    from Items i 
    where category in (select id from Categories) 
     and exists (select 1 from Bids where item_id = i.id) 
    ) as foo 
    group by foo.category 
    order by name 

Naturalmente si deve ricordare che essa strettamente dipende dai dati dei punti 1 e 2.

Se è vero 4 è possibile rimuovere esiste parte dalla query.

Eventuali consigli o idee?

0

noti che se la dimensione di bids sistematicamente e significativamente più grande items allora può effettivamente essere più conveniente per attraversare items due volte (specialmente se items inserisce nella RAM) di far salire fuori quelle ID elementi distinti dal risultato join (anche se ordinate in memoria). Inoltre, a seconda di come avviene la pipeline Postgres per estrarre i dati dalle tabelle duplicate, potrebbe esserci una penalità limitata anche in condizioni di carico avverso o di memoria (questa sarebbe una buona domanda che si potrebbe chiedere al pgsql-general). Prendere:

SELECT name, IC.cnt, BC.cnt FROM 
Categories C, 
(SELECT category, count(1) cnt from Items I GROUP BY category) IC, 
(SELECT category, count(1) cnt from Bids B INNER JOIN Items I ON (I.id = B.item_id) GROUP BY category) BC 
WHERE IC.category=C.id AND BC.category=id; 

Quanto più economico? Almeno 4x dato un numero sufficiente di cache, vale a dire 610 ms vs 2500 ms (in memoria) con 20 categorie, 50k item e 600k bids, e ancora più veloce di 2x dopo un flush della cache del filesystem sulla mia macchina.

Si noti che quanto sopra non è un sostituto diretto per la query originale; per uno si presuppone che vi sia un mapping 1: 1 tra ID di categoria e nomi (che potrebbe rivelarsi un'ipotesi molto ragionevole; in caso contrario, semplicemente SUM(BC.cnt) e SUM(IC.cnt) come GROUP BY name), ma, più importante, la categoria il numero di articoli include articoli che non hanno offerte, a differenza dell'originale INNER JOIN. Se sono richiesti solo i conteggi dell'offerta, è possibile aggiungere WHERE EXISTS (SELECT 1 FROM Bids B where item_id=I.id) nella sottoquery IC; questo attraverserà anche Bids (nel mio caso ha aggiunto una penalità di ~ 200ms al piano esistente ~ 600ms, ancora ben al di sotto dei 2400ms.)