Risposte:
Anche Explaining_EXPLAIN.pdf potrebbe aiutare.
La parte che ho sempre trovato confusa è il costo di avvio rispetto al costo totale. Lo cerco su Google ogni volta che me ne dimentico, il che mi riporta qui, il che non spiega la differenza, motivo per cui sto scrivendo questa risposta. Questo è ciò che ho raccolto dalla documentazione di PostgresEXPLAIN
, spiegato a quanto ho capito.
Ecco un esempio tratto da un'applicazione che gestisce un forum:
EXPLAIN SELECT * FROM post LIMIT 50;
Limit (cost=0.00..3.39 rows=50 width=422)
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
Ecco la spiegazione grafica di PgAdmin:
(Quando utilizzi PgAdmin, puoi puntare il mouse su un componente per leggere i dettagli del costo.)
Il costo è rappresentato come una tupla, ad esempio il costo di LIMIT
is cost=0.00..3.39
e il costo della scansione sequenziale post
è cost=0.00..15629.12
. Il primo numero nella tupla è il costo di avvio e il secondo numero è il costo totale . Poiché ho usato EXPLAIN
e non EXPLAIN ANALYZE
, questi costi sono stime, non misure effettive.
Come complicazione, i costi di ogni nodo "genitore" includono i costi dei suoi nodi figli. Nella rappresentazione testuale, l'albero è rappresentato dal rientro, ad esempio LIMIT
è un nodo genitore ed Seq Scan
è il suo figlio. Nella rappresentazione PgAdmin, le frecce puntano da figlio a genitore, la direzione del flusso di dati, il che potrebbe essere controintuitivo se si ha familiarità con la teoria dei grafi.
La documentazione dice che i costi sono comprensivi di tutti i nodi figlio, ma si noti che il costo totale del genitore 3.39
è molto inferiore al costo totale del figlio 15629.12
. Il costo totale non è comprensivo perché un componente come LIMIT
non ha bisogno di elaborare l'intero input. Vedi l' EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100 AND unique2 > 9000 LIMIT 2;
esempio nella documentazione di PostgresEXPLAIN
.
Nell'esempio sopra, il tempo di avvio è zero per entrambi i componenti, perché nessuno dei componenti deve eseguire alcuna elaborazione prima di iniziare a scrivere righe: una scansione sequenziale legge la prima riga della tabella e la emette. Il LIMIT
legge la sua prima riga e poi la emette.
Quando un componente dovrebbe eseguire molte elaborazioni prima di poter iniziare a produrre righe? Ci sono molte possibili ragioni, ma diamo un'occhiata a un chiaro esempio. Ecco la stessa query di prima ma ora contenente una ORDER BY
clausola:
EXPLAIN SELECT * FROM post ORDER BY body LIMIT 50;
Limit (cost=23283.24..23283.37 rows=50 width=422)
-> Sort (cost=23283.24..23859.27 rows=230412 width=422)
Sort Key: body
-> Seq Scan on post (cost=0.00..15629.12 rows=230412 width=422)
E graficamente:
Ancora una volta, la scansione sequenziale post
non ha alcun costo di avvio: inizia immediatamente l'output delle righe. Ma l'ordinamento ha un costo di avvio significativo 23283.24
perché deve ordinare l'intera tabella prima di poter produrre anche una singola riga . Il costo totale dell'ordinamento 23859.27
è solo leggermente superiore al costo di avvio, riflettendo il fatto che una volta che l'intero set di dati è stato ordinato, i dati ordinati possono essere emessi molto rapidamente.
Si noti che il tempo di avvio di LIMIT
23283.24
è esattamente uguale al tempo di avvio dell'ordinamento. Questo non è perché esso LIMIT
stesso ha un tempo di avvio elevato. In realtà ha un tempo di avvio pari a zero da solo, ma EXPLAIN
accumula tutti i costi del figlio per ogni genitore, quindi il LIMIT
tempo di avvio include la somma dei tempi di avvio dei suoi figli.
Questo raggruppamento dei costi può rendere difficile la comprensione del costo di esecuzione di ogni singolo componente. Ad esempio, il nostro LIMIT
ha un tempo di avvio pari a zero, ma a prima vista non è ovvio. Per questo motivo, molte altre persone si sono collegate a spiegare.depesz.com , uno strumento creato da Hubert Lubaczewski (aka depesz) che aiuta a capire EXPLAIN
, tra le altre cose, sottraendo i costi dei figli dai costi dei genitori. Cita alcune altre complessità in un breve post sul blog sul suo strumento.
Viene eseguito dal più rientrato al meno rientrato, e credo dal fondo del piano verso l'alto. (Quindi, se ci sono due sezioni rientrate, quella più in basso nella pagina viene eseguita per prima, quindi quando si incontrano viene eseguita l'altra, quindi viene eseguita la regola che le unisce.)
L'idea è che ad ogni passaggio ci siano 1 o 2 set di dati che arrivano e vengono elaborati da una regola. Se solo un set di dati, tale operazione viene eseguita su quel set di dati. (Ad esempio, scansiona un indice per capire quali righe vuoi, filtra un set di dati o ordinalo.) Se due, i due set di dati sono le due cose che sono ulteriormente rientrate e sono unite dalla regola che vedi. Il significato della maggior parte delle regole può essere ragionevolmente facilmente indovinato (in particolare se hai già letto un sacco di piani di spiegazione), tuttavia puoi provare a verificare i singoli elementi guardando nella documentazione o (più facilmente) semplicemente inserendo la frase Google insieme ad alcune parole chiave come EXPLAIN
.
Questa ovviamente non è una spiegazione completa, ma fornisce un contesto sufficiente per poter solitamente capire quello che vuoi. Ad esempio, considera questo piano da un database reale:
explain analyze
select a.attributeid, a.attributevalue, b.productid
from orderitemattribute a, orderitem b
where a.orderid = b.orderid
and a.attributeid = 'display-album'
and b.productid = 'ModernBook';
------------------------------------------------------------------------------------------------------------------------------------------------------------
Merge Join (cost=125379.14..125775.12 rows=3311 width=29) (actual time=841.478..841.478 rows=0 loops=1)
Merge Cond: (a.orderid = b.orderid)
-> Sort (cost=109737.32..109881.89 rows=57828 width=23) (actual time=736.163..774.475 rows=16815 loops=1)
Sort Key: a.orderid
Sort Method: quicksort Memory: 1695kB
-> Bitmap Heap Scan on orderitemattribute a (cost=1286.88..105163.27 rows=57828 width=23) (actual time=41.536..612.731 rows=16815 loops=1)
Recheck Cond: ((attributeid)::text = 'display-album'::text)
-> Bitmap Index Scan on (cost=0.00..1272.43 rows=57828 width=0) (actual time=25.033..25.033 rows=16815 loops=1)
Index Cond: ((attributeid)::text = 'display-album'::text)
-> Sort (cost=15641.81..15678.73 rows=14769 width=14) (actual time=14.471..16.898 rows=1109 loops=1)
Sort Key: b.orderid
Sort Method: quicksort Memory: 76kB
-> Bitmap Heap Scan on orderitem b (cost=310.96..14619.03 rows=14769 width=14) (actual time=1.865..8.480 rows=1114 loops=1)
Recheck Cond: ((productid)::text = 'ModernBook'::text)
-> Bitmap Index Scan on id_orderitem_productid (cost=0.00..307.27 rows=14769 width=0) (actual time=1.431..1.431 rows=1114 loops=1)
Index Cond: ((productid)::text = 'ModernBook'::text)
Total runtime: 842.134 ms
(17 rows)
Prova a leggerlo di persona e vedi se ha senso.
Quello che ho letto è che il database prima scansiona l' id_orderitem_productid
indice, usandolo per trovare le righe da cui vuole orderitem
, quindi ordina quel set di dati usando un quicksort (l'ordinamento usato cambierà se i dati non si adattano alla RAM), quindi lo mette da parte.
Successivamente, esegue la scansione orditematt_attributeid_idx
per trovare le righe da cui desidera orderitemattribute
e quindi ordina il set di dati utilizzando un quicksort.
Quindi prende i due set di dati e li unisce. (Un merge join è una sorta di operazione di "compressione" in cui fa scorrere i due set di dati ordinati in parallelo, emettendo la riga unita quando corrispondono.)
Come ho detto, si lavora dalla parte interna del piano alla parte esterna, dal basso verso l'alto.
È disponibile anche uno strumento di supporto online, Depesz , che evidenzierà dove si trovano le parti costose dei risultati dell'analisi.
ha anche uno, ecco gli stessi risultati , che per me rendono più chiaro dove sia il problema.
PgAdmin ti mostrerà una rappresentazione grafica del piano di spiegazione. Passare avanti e indietro tra i due può davvero aiutarti a capire cosa significa la rappresentazione del testo. Tuttavia, se vuoi solo sapere cosa farà, potresti essere in grado di usare sempre la GUI.
La documentazione ufficiale di PostgreSQL fornisce una spiegazione interessante e completa su come comprendere l'output di spiegare.