Identificare i componenti di elaborazione delle query

Completato

L'esecuzione di una query prevede quattro fasi distinte. In ordine di esecuzione queste fasi sono:

  1. Analisi in corso
  2. Trasformazione (rewriter)
  3. Pianificazione
  4. Esecuzione

Parser

Il parser è responsabile di controllare la validità della sintassi della stringa di query. Il parser è composto da due parti principali:

  • gram.y che è costituito da un set di regole di grammatica e dalle azioni corrispondenti.
  • scan.1, ovvero il lexer, che riconosce gli identificatori e le parole chiave SQL. Ogni parola chiave o identificatore attiva un token che viene creato e consegnato al parser.

Il parser compila un albero di query, che separa la query in parti identificabili per comprendere quali tabelle sono coinvolte, quali filtri sono stati applicati e così via. Le parti di un albero di query sono:

  • Tipo di comando: SELECT, INSERT, UPDATE o DELETE.
  • Voce della tabella di intervalli: elenco di relazioni, tabelle ie, sottoquery, risultati di join e così via. In un'istruzione SELECT questi elementi vengono visualizzati dopo la parola chiave FROM.
  • Relazione dei risultati: la relazione dei risultati per i comandi INSERT, UPDATE e DELETE è la tabella o la vista in cui devono avere effetto le modifiche.
  • Elenco di destinazioni: risultati della query, identificati tra le parole chiave SELECT e FROM. I comandi DELETE non producono un risultato, quindi il planner aggiunge una voce speciale per consentire all'executor di trovare la riga da eliminare. I comandi INSERT identificano le nuove righe che devono essere inserite nella relazione dei risultati. Per i comandi UPDATE, l'elenco di destinazioni descrive le nuove righe che devono sostituire quelle precedenti.
  • Qualifica: valore booleano che specifica se eseguire o meno l'operazione per la riga del risultato finale. Corrisponde alla clausola WHERE di un'istruzione SQL.
  • Albero di join: questo albero può essere un elenco degli elementi FROM. I join possono essere eseguiti in qualsiasi ordine oppure in un ordine specifico, come gli outer join.
  • Altri: elementi non pertinenti in questa fase, ad esempio la clausola ORDER BY.

Rewriter

L'output del parser viene passato al processo di trasformazione o rewriter, a meno che non venga trovato un errore, nel qual caso viene restituito un messaggio di errore.

Il rewriter riscrive il testo della query applicandovi alcune regole. Il rewriter prende in considerazione le regole e quindi passa la query modificata al planner di query. La sicurezza a livello di riga viene implementata in questa fase.

Ad esempio, le regole relative a SELECT vengono sempre applicate come ultimo passaggio, anche per le query INSERT, UPDATE e DELETE. Le regole prevedono anche che le query UPDATE non sovrascrivano le righe esistenti, ma che venga inserita una nuova riga e che la riga precedente venga nascosta. Dopo il commit della transazione, il processo vacuum può rimuovere la riga nascosta.

Planner

Il compito del planner è quello di prendere in considerazione le regole della query e capire quale dei diversi modi in cui la query potrebbe essere eseguita è il più veloce.

Il planner crea un albero del piano, con i nodi che rappresentano le operazioni fisiche sui dati.

PostgreSQL usa un componente Query Optimizer basato sui costi per trovare il piano ottimale per una query. Il planner valuta vari piani di esecuzione e stima la quantità di risorse necessarie, ad esempio cicli della CPU, operazioni di I/O e così via. Questa stima viene quindi convertita in unità, note come costo del piano. Viene selezionato il piano con il costo più basso.

Tuttavia, man mano che aumenta il numero di join, il numero di possibili piani aumenta in modo esponenziale. Valutare ogni piano potenziale diventa impossibile anche per query relativamente semplici. Per limitare il numero di piani possibili, si ricorre a euristiche e algoritmi. Il risultato è che il piano selezionato potrebbe non essere quello ottimale. Tuttavia, è quasi ottimale e selezionato in un tempo ragionevole.

Il costo è la migliore stima del planner. Lo scopo della stima dei costi è quello di confrontare piani di esecuzione diversi per la stessa query nelle stesse condizioni. Il pianificatore usa le statistiche raccolte sulle tabelle e sulle righe per produrre le stime dei costi delle query. Perché le stime dei costi siano accurate, le statistiche devono essere aggiornate.

Statistiche aggiornate

Il componente planner di Query Optimizer usa le statistiche relative a tabelle e righe per produrre stime accurate dei costi.

ANALYZE raccoglie le statistiche sulle tabelle nel database e archivia i risultati nel catalogo di sistema pg_statistic. È necessario eseguire ANALYZE se:

  • Si è disabilitato autovacuum (che in genere analizza automaticamente le tabelle)
  • Si è disabilitato autovacuum e ANALYZE non è stato eseguito di recente
  • È presente una delle condizioni precedenti e sono presenti numerose istruzioni INSERTS, UPDATES o DELETE.

Le stime dei costi si basano sulle statistiche aggiornate e, se le statistiche non sono aggiornate, è possibile che venga scelto un piano inefficiente. Quando non viene passato alcun parametro ad ANALYZE, viene esaminata ogni tabella del database.

La sintassi per ANALYZE è:

ANALYZE [ VERBOSE ] [ ***table*** [ ( ***column*** [, ...] ) ] ]

VERBOSE visualizza i messaggi di stato per mostrare la tabella di cui è in corso l'analisi, insieme ad alcune statistiche.

Programmare VACUUM e ANALYZE in modo che vengano eseguiti ogni giorno durante un periodo di basso utilizzo. ANALYZE può essere eseguito in parallelo con altre attività perché richiede solo un blocco di lettura sulla tabella di destinazione.

Executor

Questa fase accetta il piano creato dal planner e lo elabora in modo ricorsivo per estrarre il set di righe richiesto. Ogni volta che viene chiamato un nodo del piano, l'executor deve restituire una riga o comunicare che ha finito.

L'executor valuta tutti e quattro i tipi di query SQL:

  • SELECT
  • INSERT …
  • UPDATE
  • DELETE

Per SELECT, l'executor restituisce ogni riga al client come set di risultati.

Per INSERT, ogni riga restituita viene inserita nella tabella specificata. Questa attività viene eseguita in uno speciale nodo del piano di primo livello denominato ModifyTable.

Per UPDATE, ogni riga calcolata include tutti i valori di colonna aggiornati, oltre all'ID della riga di destinazione. I dati vengono inviati a un nodo ModifyTable, che crea una riga aggiornata e contrassegna la riga precedente come eliminata.

Per DELETE, l'unica colonna restituita dal piano è l'ID riga. Il nodo ModifyTable usa l'ID riga per contrassegnare la riga come eliminata.