Una domanda che è emersa in una discussione in chat:
So che hash join passa internamente a una sorta di cosa dei loop nidificati.
Cosa fa SQL Server per un salvataggio dell'hash aggregate (se ciò può accadere)?
Una domanda che è emersa in una discussione in chat:
So che hash join passa internamente a una sorta di cosa dei loop nidificati.
Cosa fa SQL Server per un salvataggio dell'hash aggregate (se ciò può accadere)?
Risposte:
L'hash join e l' aggregazione hash utilizzano entrambi lo stesso codice operatore internamente, sebbene un aggregato hash utilizzi solo un singolo input (build). L'operazione di base dell'hash aggregate è descritta da Craig Freedman :
Come con hash join, l'aggregazione hash richiede memoria. Prima di eseguire una query con un aggregato hash, SQL Server utilizza le stime di cardinalità per stimare la quantità di memoria necessaria per eseguire la query. Con un hash join, memorizziamo ogni riga di build, quindi il fabbisogno di memoria totale è proporzionale al numero e alla dimensione delle righe di build. Il numero di righe che si uniscono e la cardinalità di output del join non influiscono sui requisiti di memoria del join. Con un aggregato di hash, memorizziamo una riga per ciascun gruppo, quindi il fabbisogno di memoria totale è effettivamente proporzionale al numero e alla dimensione dei gruppi o delle righe di output. Se abbiamo meno valori univoci del gruppo per colonna (e) e meno gruppi, abbiamo bisogno di meno memoria. Se abbiamo valori più univoci del gruppo per colonna (e) e più gruppi, abbiamo bisogno di più memoria.
Continua a parlare della ricorsione dell'hash:
Quindi, cosa succede se esauriamo la memoria? Ancora una volta, come hash join, se esauriamo la memoria, dobbiamo iniziare a riversare le righe su tempdb. Distribuiamo uno o più bucket o partizioni, inclusi eventuali risultati parzialmente aggregati, insieme a eventuali nuove righe aggiuntive che hanno hash sui bucket o sulle partizioni rovesciati. Sebbene non cerchiamo di aggregare le nuove righe rovesciate, le facciamo hash e le suddividiamo in diversi bucket o partizioni. Una volta terminata l'elaborazione di tutti i gruppi di input, eseguiamo l'output dei gruppi in memoria completati e ripetiamo l'algoritmo rileggendo e aggregando una partizione rovesciata alla volta. Dividendo le righe sparse in più partizioni, riduciamo le dimensioni di ciascuna partizione e, quindi, riduciamo il rischio che l'algoritmo dovrà ripetere più volte.
Il salvataggio dell'hash è leggermente documentato, ma menzionato da Nacho Alonso Portillo in Qual è il livello massimo di ricorsione per l'iteratore di hash prima di forzare il salvataggio?
Il valore è costante, codificato nel prodotto e il suo valore è cinque (5). Ciò significa che prima che l'operatore di hash scan ricorra a un algoritmo basato sull'ordinamento per una determinata sottopartizione che non si adatta alla memoria concessa dallo spazio di lavoro, devono essere avvenuti cinque tentativi precedenti di suddividere la partizione originale in partizioni più piccole.
L '"operatore di scansione hash" menzionato è un riferimento alla classe interna CQScanHash
in sqlmin.dll
. Questa classe guida l'implementazione dell'operatore hash (in tutte le sue forme, inclusi aggregati parziali e flusso distinti) che vediamo nei piani di esecuzione.
Questo ci porta al cuore delle tue domande: cosa fa esattamente l'algoritmo di salvataggio? È "basato sull'ordinamento" o basato su "una sorta di cosa dei cicli nidificati"?
Probabilmente è entrambi, a seconda del tuo punto di vista. Quando la ricorsione dell'hash raggiunge il livello 5, la partizione hash in memoria passa da una tabella hash a un indice b-tree inizialmente vuoto sui valori hash. Ogni riga da una singola partizione hash precedentemente versata viene cercata nell'indice b-tree e inserita (nuovo gruppo) o aggiornata (mantenendo aggregati) come appropriato.
Questa serie di inserti non ordinati in un b-tree può ugualmente essere vista come un ordinamento di inserzione o come una ricerca di cicli nidificati indicizzati.
In ogni caso, questo algoritmo di fallback è garantito alla fine senza allocare più memoria. Potrebbe richiedere più passaggi se lo spazio disponibile per l'albero b non è sufficiente per contenere tutte le chiavi di raggruppamento e gli aggregati dalla partizione di overflow.
Una volta esaurita la memoria disponibile per contenere l'indice b-tree, eventuali ulteriori righe (dalla partizione corrente versata) vengono inviate a una singola nuova partizione tempdb (che è garantita essere più piccola) e il processo si ripete se necessario. Il livello di sversamento rimane a 5 perché la ricorsione dell'hash è terminata. Alcuni dettagli di elaborazione possono essere osservati con flag di traccia non documentato 7357.