In questo articolo, toccheremo l'argomento delle prestazioni delle variabili di tabella. In SQL Server possiamo creare variabili che funzioneranno come tabelle complete. Forse altri database hanno le stesse capacità, tuttavia, ho usato tali variabili solo in MS SQL Server.
Pertanto, puoi scrivere quanto segue:
declare @t as table (int value)
Qui dichiariamo la variabile @t come una tabella che conterrà una singola colonna Value di tipo Integer. È possibile creare tabelle più complesse, tuttavia, nel nostro esempio, una colonna è sufficiente per esplorare l'ottimizzazione.
Ora possiamo usare questa variabile nelle nostre query. Possiamo aggiungere molti dati ad esso ed eseguire il recupero dei dati da questa variabile:
insert into @t select UserID from User or select * from @t
Ho notato che le variabili di tabella vengono utilizzate quando è necessario recuperare i dati per un'ampia selezione. Ad esempio, nel codice è presente una query che restituisce gli utenti del sito. Ora raccogli gli ID di tutti gli utenti, li aggiungi alla variabile della tabella e puoi cercare gli indirizzi per questi utenti. Forse qualcuno potrebbe chiedersi perché non eseguiamo una query sul database e otteniamo tutto subito? Ho un semplice esempio.
Si supponga che gli utenti provengano dal servizio Web, mentre i loro indirizzi sono archiviati nel database. In questo caso, non c'è via d'uscita. Abbiamo ricevuto un sacco di ID utente dal servizio e, per evitare di interrogare il database, qualcuno decide che è più facile aggiungere tutti gli ID al parametro di query come variabile di tabella e la query apparirà ordinata:
select * from @t as users join Address a on a.UserID = users.UserID os
Tutto questo funziona correttamente. Nel codice C# puoi combinare rapidamente i risultati di entrambe le matrici di dati in un unico oggetto usando LINQ. Tuttavia, le prestazioni della query potrebbero risentirne.
Il fatto è che le variabili di tabella non sono state progettate per elaborare grandi volumi di dati. Se non sbaglio, Query Optimizer utilizzerà sempre il metodo di esecuzione LOOP. Pertanto, per ogni ID di @t, verrà eseguita una ricerca nella tabella Indirizzo. Se ci sono 1000 record in @t, il server eseguirà la scansione dell'indirizzo 1000 volte.
In termini di esecuzione, a causa del numero folle di scansioni, il server semplicemente si interrompe cercando di trovare i dati.
È molto più efficace scansionare l'intera tabella degli indirizzi e trovare tutti gli utenti contemporaneamente. Questo metodo è chiamato MERGE. Tuttavia, SQL Server lo sceglie quando sono presenti molti dati ordinati. In questo caso, l'ottimizzatore non sa quanti e quali dati verranno aggiunti alla variabile e se esiste un ordinamento perché tale variabile non include indici.
Se ci sono pochi dati nella variabile della tabella e non si inseriscono migliaia di righe in essa, va tutto bene. Tuttavia, se ti piace utilizzare tali variabili e aggiungere un'enorme quantità di dati, devi continuare a leggere.
Anche se sostituisci la variabile della tabella con SQL, accelererà notevolmente le prestazioni della query:
select * from ( Select 10377 as UserID Union all Select 73736 Union all Select 7474748 …. ) as users join Address a on a.UserID = users.UserID
Potrebbero esserci migliaia di tali istruzioni SELECT e il testo della query sarà enorme, ma verrà eseguito migliaia di volte più velocemente per una grande quantità di dati perché SQL Server può scegliere un piano di esecuzione efficace.
Questa query non sembra eccezionale. Tuttavia, il suo piano di esecuzione non può essere memorizzato nella cache perché la modifica di un solo ID cambierà anche l'intero testo della query e i parametri non possono essere utilizzati.
Penso che Microsoft non si aspettasse che gli utenti utilizzassero le variabili tabulari in questo modo, ma c'è una bella soluzione alternativa.
Ci sono diversi modi per risolvere questo problema. Tuttavia, secondo me, il più efficace in termini di prestazioni è aggiungere OPTION (RICIMPILA) alla fine della query:
select * from @t as users join Address a on a.UserID = users.UserID OPTION (RECOMPILE)
Questa opzione viene aggiunta una volta alla fine della query dopo anche ORDER BY. Lo scopo di questa opzione è fare in modo che SQL Server ricompili la query a ogni esecuzione.
Se misuriamo le prestazioni della query in seguito, molto probabilmente il tempo per eseguire la ricerca sarà ridotto. Con dati di grandi dimensioni, il miglioramento delle prestazioni può essere significativo, da decine di minuti a secondi. Ora, il server compila il suo codice prima di eseguire ogni query e non utilizza il piano di esecuzione dalla cache, ma ne genera uno nuovo, a seconda della quantità di dati nella variabile, e questo di solito aiuta molto.
Lo svantaggio è che il piano di esecuzione non viene memorizzato e il server deve compilare la query e cercare ogni volta un piano di esecuzione efficace. Tuttavia, non ho visto le query in cui questo processo ha richiesto più di 100 ms.
È una cattiva idea usare le variabili di tabella? No non lo è. Ricorda solo che non sono stati creati per dati di grandi dimensioni. A volte, è meglio creare una tabella temporanea, se ci sono molti dati, e inserire dati in questa tabella, o anche creare un indice al volo. Ho dovuto farlo con i rapporti, anche se solo una volta. Allora, ho ridotto il tempo per la generazione di un rapporto da 3 ore a 20 minuti.
Preferisco usare una grande query invece di dividerla in più query e memorizzare i risultati in variabili. Consenti a SQL Server di ottimizzare le prestazioni di una query di grandi dimensioni e non ti deluderà. Tieni presente che dovresti ricorrere alle variabili di tabella solo in casi estremi quando ne vedi davvero i vantaggi.