Questa serie in cinque parti approfondisce il modo in cui vengono avviati i piani paralleli in modalità riga di SQL Server. Questa prima parte copre il ruolo del compito principale (coordinatore) nella preparazione del piano per l'esecuzione parallela. Include l'inizializzazione di ciascun operatore e l'aggiunta di profiler nascosti per raccogliere dati sulle prestazioni di runtime come il conteggio effettivo delle righe e il tempo trascorso.
Configurazione
Per fornire una base concreta per l'analisi, seguiremo come inizia l'esecuzione di una particolare query parallela. Ho utilizzato lo Stack Overflow 2013 pubblico database (scarica dettagli). La forma del piano desiderata può anche essere ottenuta rispetto al set di dati Stack Overflow 2010 più piccolo, se è più conveniente. Può essere scaricato allo stesso link. Ho aggiunto un indice non cluster:
CREATE NONCLUSTERED INDEX PP ON dbo.Posts ( PostTypeId ASC, CreationDate ASC );
Il mio ambiente di test è SQL Server 2019 CU9 su un laptop con 8 core e 16 GB di memoria allocati all'istanza. Livello di compatibilità 150 viene utilizzato esclusivamente. Cito questi dettagli per aiutarti a riprodurre il piano target, se lo desideri. I fondamenti dell'esecuzione parallela in modalità riga non sono cambiati da SQL Server 2005, quindi la discussione seguente è ampiamente applicabile.
La query di prova restituisce il numero totale di domande e risposte, raggruppate per mese e anno:
WITH MonthlyPosts AS ( SELECT P.PostTypeId, CA.TheYear, CA.TheMonth, Latest = MAX(P.CreationDate) FROM dbo.Posts AS P CROSS APPLY ( VALUES ( YEAR(P.CreationDate), MONTH(P.CreationDate) ) ) AS CA (TheYear, TheMonth) GROUP BY P.PostTypeId, CA.TheYear, CA.TheMonth ) SELECT rn = ROW_NUMBER() OVER ( ORDER BY Q.TheYear, Q.TheMonth), Q.TheYear, Q.TheMonth, LatestQuestion = Q.Latest, LatestAnswer = A.Latest FROM MonthlyPosts AS Q JOIN MonthlyPosts AS A ON A.TheYear = Q.TheYear AND A.TheMonth = Q.TheMonth WHERE Q.PostTypeId = 1 AND A.PostTypeId = 2 ORDER BY Q.TheYear, Q.TheMonth OPTION ( USE HINT ('DISALLOW_BATCH_MODE'), USE HINT ('FORCE_DEFAULT_CARDINALITY_ESTIMATION'), ORDER GROUP, MAXDOP 2 );
Ho usato i suggerimenti per ottenere un particolare piano della modalità riga di forma. L'esecuzione è limitata a DOP 2 per rendere più concisi alcuni dettagli mostrati in seguito.
Il piano di esecuzione stimato è (clicca per ingrandire):
Sfondo
Query Optimizer produce un unico piano compilato per un batch. Ogni istruzione nel batch è contrassegnata per l'esecuzione seriale o parallela, a seconda dell'idoneità e dei costi stimati.
Un piano parallelo contiene scambi (operatori di parallelismo). Gli scambi possono essere visualizzati in stream di distribuzione , stream di ripartizioni o raccogliere stream modulo. Ciascuno di questi tipi di scambio utilizza gli stessi componenti sottostanti, solo cablati in modo diverso, con un numero diverso di input e output. Per ulteriori informazioni sull'esecuzione parallela in modalità riga, vedere Piani di esecuzione parallela:rami e thread.
downgrade DOP
Il grado di parallelismo (DOP) per un piano parallelo può essere declassato in fase di esecuzione, se necessario. Una query parallela potrebbe iniziare richiedendo DOP 8, ma essere progressivamente declassata a DOP 4, DOP 2 e infine DOP 1 a causa della mancanza di risorse di sistema in quel momento. Se vuoi vederlo in azione, guarda questo breve video di Erik Darling.
L'esecuzione di un piano parallelo su un singolo thread può verificarsi anche quando un piano parallelo memorizzato nella cache viene riutilizzato da una sessione limitata a DOP 1 da un'impostazione ambientale (ad es. maschera di affinità o governatore delle risorse). Per i dettagli, vedere il mito:SQL Server memorizza nella cache un piano seriale con ogni piano parallelo.
Qualunque sia la causa, il downgrade DOP di un piano parallelo memorizzato nella cache non comportare la compilazione di un nuovo piano seriale. SQL Server riutilizza il piano parallelo esistente disabilitando gli scambi. Il risultato è un piano "parallelo" che viene eseguito su un singolo thread. Gli scambi vengono ancora visualizzati nel piano, ma vengono ignorati in fase di esecuzione.
SQL Server non può promuovere un piano seriale all'esecuzione parallela aggiungendo scambi in fase di esecuzione. Ciò richiederebbe una nuova compilazione.
Inizializzazione del piano parallelo
Un piano parallelo ha tutti gli scambi necessari per utilizzare thread di lavoro extra, ma c'è un lavoro di configurazione aggiuntivo necessario in fase di esecuzione prima che possa iniziare l'esecuzione parallela. Un esempio ovvio è che i thread di lavoro aggiuntivi devono essere assegnati a compiti specifici all'interno del piano, ma c'è molto di più.
Inizierò dal punto in cui è stato recuperato un piano parallelo dalla cache del piano. A questo punto, esiste solo il thread originale che elabora la richiesta corrente. Questo thread è talvolta chiamato "thread coordinatore" nei piani paralleli, ma preferisco i termini "attività principale ” o “genitore lavoratore”. Altrimenti non c'è niente di speciale in questo thread; è lo stesso thread utilizzato dalla connessione per elaborare le richieste dei client ed eseguire i piani seriali fino al completamento.
Per sottolineare il fatto che esiste un solo thread in questo momento, voglio che visualizzi il piano in questo momento in questo modo:
Userò screenshot di Sentry One Plan Explorer quasi esclusivamente in questo post, ma solo per questa prima visualizzazione mostrerò anche la versione SSMS:
In entrambe le rappresentazioni, la differenza fondamentale è la mancanza di icone di parallelismo su ciascun operatore, anche se gli scambi sono ancora presenti. Al momento esiste solo l'attività padre, in esecuzione sul thread di connessione originale. Nessun thread di lavoro aggiuntivo sono state già prenotate, create o assegnate attività. Mantieni la rappresentazione del piano di cui sopra in primo piano mentre procediamo.
Creazione del piano eseguibile
Il piano a questo punto è essenzialmente solo un modello che può essere utilizzato come base per qualsiasi esecuzione futura. Per prepararlo per un'esecuzione specifica, SQL Server deve compilare i valori di runtime come l'utente corrente, il contesto della transazione, i valori dei parametri, gli ID per tutti gli oggetti creati in fase di esecuzione (ad es. tabelle e variabili temporanee) e così via.
Per un piano parallelo, SQL Server deve eseguire un bel po' di lavoro preparatorio aggiuntivo per portare il macchinario interno al punto in cui può iniziare l'esecuzione. Il thread di lavoro dell'attività padre è responsabile dell'esecuzione di quasi tutto questo lavoro (e certamente di tutto il lavoro che tratteremo nella parte 1).
Il processo di trasformazione del modello di piano per un'esecuzione specifica è noto come creazione del piano eseguibile . A volte è difficile mantenere chiara la terminologia, perché i termini sono spesso sovraccaricati e applicati in modo errato (anche da Microsoft), ma farò del mio meglio per essere il più coerente possibile.
Contesti di esecuzione
Puoi pensare a un contesto di esecuzione come modello di piano popolato con tutte le informazioni di runtime specifiche necessarie per un particolare thread. Il piano eseguibile per un seriale istruzione consiste in un unico contesto di esecuzione, in cui un singolo thread esegue l'intero piano.
Un parallelo piano eseguibile contiene una raccolta di contesti di esecuzione :uno per l'attività padre e uno per thread in ogni ramo parallelo. Ogni thread di lavoro parallelo aggiuntivo esegue la sua parte del piano generale all'interno del proprio contesto di esecuzione. Ad esempio, un piano parallelo con tre rami in esecuzione su DOP 8 ha (1 + (3 * 8)) =25 contesti di esecuzione. I contesti di esecuzione seriale vengono memorizzati nella cache per il riutilizzo, ma non i contesti di esecuzione parallela aggiuntivi.
L'attività padre esiste sempre prima di qualsiasi attività parallela aggiuntiva, quindi le viene assegnato contesto di esecuzione zero . I contesti di esecuzione utilizzati dai lavoratori paralleli verranno creati in seguito, dopo che il contesto padre è stato completamente inizializzato. I contesti aggiuntivi vengono clonati dal contesto principale, quindi personalizzato per il loro compito specifico (questo è trattato nella parte 2).
Ci sono una serie di attività coinvolte nell'avvio del contesto di esecuzione zero. Non è pratico tentare di elencarli tutti, ma sarà utile coprire alcuni dei principali applicabili alla nostra query di test. Ce ne saranno ancora troppi per un unico elenco, quindi li suddividerò in sezioni (piuttosto arbitrarie):
1. Inizializzazione del contesto principale
Quando inviamo l'istruzione per l'esecuzione, il contesto dell'attività padre (contesto di esecuzione zero) viene inizializzato con:
- Un riferimento alla transazione di base (esplicito, implicito o autocommit). I lavoratori paralleli eseguiranno sottotransazioni, ma sono tutti inclusi nell'ambito della transazione di base.
- Un elenco di parametri di istruzione e i loro valori attuali.
- Un oggetto di memoria principale (PMO) utilizzato per gestire le concessioni e le allocazioni di memoria.
- Una mappa collegata degli operatori (nodi di query) nel piano eseguibile.
- Una fabbrica per qualsiasi oggetto di grandi dimensioni richiesto maniglie (blob).
- Classi di blocco per tenere traccia di più blocchi mantenuti per un periodo durante l'esecuzione. Non tutti i piani richiedono classi di blocco poiché gli operatori di streaming completo in genere bloccano e sbloccano singole righe in sequenza.
- La stima della concessione di memoria per la query.
- Concessione di memoria in modalità riga feedback strutture per ciascun operatore (SQL Server 2019).
Molte di queste cose verranno utilizzate o referenziate da attività parallele in seguito, quindi devono esistere prima nell'ambito padre.
2. Metadati del contesto principale
I prossimi compiti principali eseguiti sono:
- Verifica del costo della query stimato rientra nel limite impostato dalle opzioni di configurazione del limite di costo del governatore della query.
- Aggiornamento dell'utilizzo dell'indice record – esposto tramite
sys.dm_db_index_usage_stats
. - Creazione di valori di espressione memorizzati nella cache (costanti di runtime).
- Creazione di un elenco di operatori di profilazione utilizzato per raccogliere le metriche di runtime come il conteggio delle righe e i tempi, se richiesto per l'esecuzione corrente. I profiler stessi non sono ancora stati creati, solo l'elenco.
- Scattare un'istantanea di attende per la funzione di attesa della sessione esposta tramite
sys.dm_exec_session_wait_stats
.
3. DOP e concessione di memoria
Il contesto dell'attività principale ora:
- Calcola il grado di parallelismo di runtime (DOP ). Ciò è influenzato dal numero di lavoratori liberi (vedi "downgrade DOP" in precedenza), dove possono essere collocati tra i nodi, e da un numero di flag di traccia.
- Riserva il numero richiesto di thread. Questo passaggio è pura contabilità:i thread stessi potrebbero non esistere a questo punto. SQL Server tiene traccia del numero massimo di thread che può avere. Prenotazione di thread sottrae da quel numero. Quando i fili sono finiti, il numero massimo viene nuovamente aumentato.
- Imposta il timeout concessione memoria .
- Calcola la concessione di memoria, inclusa la memoria richiesta per i buffer di scambio.
- Acquisisce la concessione di memoria tramite il semaforo delle risorse appropriato .
- Crea un oggetto manager per gestire sottoprocessi paralleli . L'attività principale è il processo di livello superiore; attività aggiuntive sono anche note come sottoprocessi .
Sebbene i thread siano "riservati" a questo punto, SQL Server potrebbe comunque riscontrare THREADPOOL
attende più tardi quando tenta di utilizzare uno dei thread "riservati". La prenotazione garantisce che SQL Server rimanga sempre attorno al numero massimo di thread configurato, ma il thread fisico potrebbe non essere immediatamente disponibile dal pool di thread . Quando ciò accade, il sistema operativo dovrà avviare un nuovo thread, operazione che può richiedere un po' di tempo. Per ulteriori informazioni, vedere Unusual THREADPOOL Waits di Josh Darnell.
4. Configurazione scansione query
I piani in modalità riga vengono eseguiti in iterativo moda, partendo dalla radice. Il piano che abbiamo al momento non è ancora capace di questa modalità di esecuzione. È ancora in gran parte un modello, anche se contiene già una discreta quantità di informazioni specifiche sull'esecuzione.
SQL Server deve convertire la struttura corrente in un albero di iteratori , ognuno con metodi come Open
, GetRow
e Close
. I metodi iteratori sono collegati ai loro figli tramite puntatori a funzione. Ad esempio, chiamando GetRow
sulla radice chiama ricorsivamente GetRow
sugli operatori figli fino a quando non viene raggiunto il livello di una foglia e una fila inizia a "ribollire" sull'albero. Per un aggiornamento sui dettagli, consulta Iteratori, piani di query e perché funzionano all'indietro.
Fine della parte 1
Abbiamo fatto buoni progressi nella configurazione del contesto di esecuzione per l'attività padre. Nella parte 2, seguiremo la costruzione dell'albero di scansione delle query necessario per l'esecuzione iterativa di SQL Server.