Sto per riscrivere del codice piuttosto vecchio usando il BULK INSERT
comando di SQL Server perché lo schema è cambiato e mi è venuto in mente che forse avrei dovuto pensare di passare a una stored procedure con un TVP, ma mi chiedo quale effetto potrebbe avere sulle prestazioni.
Alcune informazioni di base che potrebbero aiutare a spiegare perché sto facendo questa domanda:
I dati arrivano effettivamente tramite un servizio web. Il servizio Web scrive un file di testo in una cartella condivisa sul server database che a sua volta esegue un file
BULK INSERT
. Questo processo è stato originariamente implementato su SQL Server 2000 e all'epoca non c'era davvero altra alternativa che lanciare alcune centinaia diINSERT
istruzioni sul server, che in realtà era il processo originale ed era un disastro delle prestazioni.I dati vengono inseriti in blocco in una tabella di staging permanente e quindi uniti in una tabella molto più grande (dopodiché vengono eliminati dalla tabella di staging).
La quantità di dati da inserire è "grande", ma non "enorme" - di solito poche centinaia di righe, forse 5-10.000 righe al massimo in rari casi. Pertanto il mio intestino sensazione è che
BULK INSERT
essendo un'operazione non registrata non farà che differenza grande un (ma ovviamente non sono sicuro, da qui la domanda).L'inserimento è in realtà parte di un processo batch pipeline molto più ampio e deve avvenire molte volte in successione; quindi le prestazioni sono fondamentali.
I motivi per cui vorrei sostituire il BULK INSERT
con un TVP sono:
Scrivere il file di testo su NetBIOS probabilmente sta già costando del tempo, ed è piuttosto raccapricciante dal punto di vista architettonico.
Credo che il tavolo di staging possa (e dovrebbe) essere eliminato. Il motivo principale è che i dati inseriti devono essere utilizzati per un paio di altri aggiornamenti contemporaneamente all'inserimento, ed è molto più costoso tentare l'aggiornamento dalla tabella di produzione massiccia piuttosto che utilizzare uno staging quasi vuoto tavolo. Con un TVP, il parametro fondamentalmente è la tabella di staging, posso fare tutto ciò che voglio con esso prima / dopo l'inserimento principale.
Potrei praticamente eliminare il controllo degli duplicati, il codice di pulizia e tutto il sovraccarico associato agli inserimenti di massa.
Non c'è bisogno di preoccuparsi della contesa di blocco sulla tabella di staging o tempdb se il server riceve alcune di queste transazioni contemporaneamente (cerchiamo di evitarlo, ma succede).
Ovviamente ne traccerò un profilo prima di mettere qualsiasi cosa in produzione, ma ho pensato che potrebbe essere una buona idea chiedere in giro prima di passare tutto il tempo, vedere se qualcuno ha qualche severo avvertimento da emettere sull'uso dei TVP per questo scopo.
Quindi, per chiunque sia abbastanza a suo agio con SQL Server 2008 da aver provato o almeno studiato questo, qual è il verdetto? Per inserti di, diciamo, da poche centinaia a poche migliaia di righe, che avvengono su una base abbastanza frequente, i TVP tagliano la senape? C'è una differenza significativa nelle prestazioni rispetto agli inserti sfusi?
Aggiornamento: ora con il 92% di punti interrogativi in meno!
(AKA: risultati dei test)
Il risultato finale è ora in produzione dopo quello che sembra un processo di distribuzione in 36 fasi. Entrambe le soluzioni sono state ampiamente testate:
- Estrarre il codice della cartella condivisa e utilizzare il file
SqlBulkCopy
direttamente classe; - Passaggio a una stored procedure con TVP.
Solo così i lettori possono avere un'idea di cosa esattamente è stato testato, per fugare ogni dubbio sull'affidabilità di questi dati, ecco una spiegazione più dettagliata di ciò che effettivamente fa questo processo di importazione :
Inizia con una sequenza di dati temporali che normalmente è di circa 20-50 punti dati (anche se a volte può arrivare a poche centinaia);
Esegui un sacco di elaborazione folle su di esso che è per lo più indipendente dal database. Questo processo è parallelizzato, quindi circa 8-10 delle sequenze in (1) vengono elaborate contemporaneamente. Ogni processo parallelo genera 3 sequenze aggiuntive.
Prendi tutte e 3 le sequenze e la sequenza originale e combinale in un lotto.
Combina i batch di tutte le 8-10 attività di elaborazione ora terminate in un unico grande super batch.
Importalo utilizzando la
BULK INSERT
strategia (vedi passaggio successivo) o la strategia TVP (vai al passaggio 8).Usa la
SqlBulkCopy
classe per scaricare l'intero super-batch in 4 tabelle di staging permanenti.Eseguire una stored procedure che (a) esegua una serie di passaggi di aggregazione su 2 delle tabelle, incluse diverse
JOIN
condizioni, e poi (b) esegue unaMERGE
su 6 tabelle di produzione utilizzando sia i dati aggregati che non aggregati. (Finito)O
Genera 4
DataTable
oggetti contenenti i dati da unire; 3 di loro contengono tipi CLR che sfortunatamente non sono adeguatamente supportati dai TVP ADO.NET, quindi devono essere inseriti come rappresentazioni di stringa, il che danneggia un po 'le prestazioni.Fornire i TVP a una procedura memorizzata, che essenzialmente esegue la stessa elaborazione di (7), ma direttamente con le tabelle ricevute. (Finito)
I risultati erano ragionevolmente vicini, ma l'approccio TVP alla fine si è comportato meglio in media, anche quando i dati superavano di poco le 1000 righe.
Tieni presente che questo processo di importazione viene eseguito molte migliaia di volte in successione, quindi è stato molto facile ottenere un tempo medio semplicemente contando quante ore (sì, ore) sono state necessarie per completare tutte le unioni.
In origine, un'unione media richiedeva quasi esattamente 8 secondi per essere completata (sotto carico normale). La rimozione di NetBIOS kludge e il passaggio a hanno SqlBulkCopy
ridotto il tempo a quasi esattamente 7 secondi. Il passaggio a TVP ha ulteriormente ridotto il tempo a 5,2 secondi per batch. Si tratta di un miglioramento del 35% della produttività per un processo il cui tempo di esecuzione è misurato in ore, quindi non è affatto male. È anche un miglioramento del 25% circa SqlBulkCopy
.
In realtà sono abbastanza fiducioso che il vero miglioramento sia stato significativamente più di questo. Durante i test è emerso che l'unione finale non era più il percorso critico; invece, il servizio Web che stava eseguendo tutta l'elaborazione dei dati stava iniziando a cedere a causa del numero di richieste in arrivo. Né la CPU né l'I / O del database erano realmente esauriti e non c'era alcuna significativa attività di blocco. In alcuni casi abbiamo visto un intervallo di pochi secondi di inattività tra le unioni successive. C'era un leggero divario, ma molto più piccolo (mezzo secondo o giù di lì) durante l'uso SqlBulkCopy
. Ma suppongo che diventerà una favola per un altro giorno.
Conclusione: i parametri con valori di tabella funzionano davvero meglio delle BULK INSERT
operazioni per processi complessi di importazione + trasformazione che operano su set di dati di medie dimensioni.
Vorrei aggiungere un altro punto, solo per alleviare qualsiasi apprensione da parte delle persone che sono a favore dei tavoli di messa in scena. In un certo senso, l'intero servizio è un gigantesco processo di messa in scena. Ogni fase del processo è fortemente controllata, quindi non abbiamo bisogno di una tabella di staging per determinare il motivo per cui una particolare unione non è riuscita (sebbene in pratica non si verifichi quasi mai). Tutto quello che dobbiamo fare è impostare un flag di debug nel servizio e si interromperà nel debugger o scaricherà i suoi dati in un file invece che nel database.
In altre parole, abbiamo già una visione più che sufficiente del processo e non abbiamo bisogno della sicurezza di un tavolo di staging; L'unico motivo per cui abbiamo avuto il tavolo di staging in primo luogo è stato quello di evitare di battere su tutte le dichiarazioni INSERT
e UPDATE
che avremmo dovuto usare altrimenti. Nel processo originale, i dati di staging vivevano comunque nella tabella di staging solo per frazioni di secondo, quindi non aggiungevano valore in termini di manutenzione / manutenibilità.
Si noti inoltre che abbiamo non sostituiti ogni singola BULK INSERT
operazione con TVPs. Diverse operazioni che si occupano di grandi quantità di dati e / o non hanno bisogno di fare nulla di speciale con i dati oltre a lanciarli nel DB ancora in uso SqlBulkCopy
. Non sto suggerendo che i TVP siano una panacea per le prestazioni, ma solo che sono riusciti SqlBulkCopy
in questo caso specifico coinvolgendo diverse trasformazioni tra la messa in scena iniziale e la fusione finale.
Così il gioco è fatto. Il punto va a TToni per aver trovato il link più rilevante, ma apprezzo anche le altre risposte. Grazie ancora!