Prestazioni di bcp / BULK INSERT rispetto ai parametri con valori di tabella


84

Sto per riscrivere del codice piuttosto vecchio usando il BULK INSERTcomando 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 di INSERTistruzioni 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 INSERTessendo 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 INSERTcon 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 :

  1. Inizia con una sequenza di dati temporali che normalmente è di circa 20-50 punti dati (anche se a volte può arrivare a poche centinaia);

  2. 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.

  3. Prendi tutte e 3 le sequenze e la sequenza originale e combinale in un lotto.

  4. Combina i batch di tutte le 8-10 attività di elaborazione ora terminate in un unico grande super batch.

  5. Importalo utilizzando la BULK INSERTstrategia (vedi passaggio successivo) o la strategia TVP (vai al passaggio 8).

  6. Usa la SqlBulkCopyclasse per scaricare l'intero super-batch in 4 tabelle di staging permanenti.

  7. Eseguire una stored procedure che (a) esegua una serie di passaggi di aggregazione su 2 delle tabelle, incluse diverse JOINcondizioni, e poi (b) esegue una MERGEsu 6 tabelle di produzione utilizzando sia i dati aggregati che non aggregati. (Finito)

    O

  8. Genera 4 DataTableoggetti 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.

  9. 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 SqlBulkCopyridotto 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 INSERToperazioni 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 INSERTe UPDATEche 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 INSERToperazione 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 SqlBulkCopyin 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!


Questa è una domanda incredibile di per sé, credo che la parte di aggiornamento dovrebbe essere in una risposta;)
Marc.2377

Risposte:


10

Non ho ancora esperienza con TVP, tuttavia c'è una bella tabella di confronto delle prestazioni rispetto a BULK INSERT in MSDN qui .

Dicono che BULK INSERT ha un costo di avvio più elevato, ma è più veloce da allora in poi. In uno scenario di client remoto, tracciano la linea a circa 1000 righe (per logica server "semplice"). A giudicare dalla loro descrizione, direi che dovresti stare bene usando i TVP. Il successo in termini di prestazioni - se del caso - è probabilmente trascurabile ei vantaggi architettonici sembrano molto buoni.

Modifica: in una nota a margine è possibile evitare il file locale del server e continuare a utilizzare la copia di massa utilizzando l'oggetto SqlBulkCopy. Basta popolare un DataTable e inserirlo nel "WriteToServer" -Method di un'istanza SqlBulkCopy. Facile da usare e molto veloce.


Grazie per il collegamento, in realtà è abbastanza utile in quanto MS sembra consigliare TVP quando i dati alimentano una logica complessa (cosa che fa) e abbiamo anche la possibilità di aumentare o diminuire la dimensione del batch in modo da non andare troppo oltre Punto dolente di 1k fila. Sulla base di ciò, potrebbe valere la pena di provare almeno a vedere, anche se finisce per essere troppo lento.
Aaronaught

Sì, il collegamento è interessante. @Aaronaught - in situazioni come questa, vale sempre la pena esplorare e analizzare le prestazioni di potenziali approcci, quindi sarei interessato a conoscere le tue scoperte!
AdaTheDev

7

Il grafico menzionato riguardo al link fornito nella risposta di @ TToni deve essere preso nel contesto. Non sono sicuro di quanta ricerca effettiva sia stata effettuata su tali raccomandazioni (si noti inoltre che il grafico sembra essere disponibile solo nelle versioni 2008e 2008 R2di quella documentazione).

D'altra parte c'è questo white paper del team di consulenza per i clienti di SQL Server: Massimizzare il throughput con TVP

Uso TVP dal 2009 e ho scoperto, almeno nella mia esperienza, che per qualcosa di diverso dal semplice inserimento in una tabella di destinazione senza necessità di logica aggiuntiva (cosa che raramente accade), i TVP sono in genere l'opzione migliore.

Tendo ad evitare le tabelle di staging poiché la convalida dei dati dovrebbe essere eseguita a livello di app. Usando i TVP, questo è facilmente sistemato e la variabile di tabella TVP nella stored procedure è, per sua stessa natura, una tabella di staging localizzata (quindi nessun conflitto con altri processi in esecuzione contemporaneamente come si ottiene quando si utilizza una tabella reale per lo staging ).

Per quanto riguarda il test eseguito nella Domanda, penso che potrebbe essere dimostrato di essere ancora più veloce di quanto originariamente trovato:

  1. Non dovresti usare un DataTable, a meno che la tua applicazione non lo usi al di fuori dell'invio dei valori al TVP. L'utilizzo IEnumerable<SqlDataRecord>dell'interfaccia è più veloce e utilizza meno memoria poiché non si duplica la raccolta in memoria solo per inviarla al DB. L'ho documentato nei seguenti luoghi:
  2. I TVP sono variabili di tabella e come tali non mantengono statistiche. Significa che segnalano di avere solo una riga in Query Optimizer. Quindi, nel tuo proc, o:
    • Usa la ricompilazione a livello di istruzione su qualsiasi query utilizzando TVP per qualcosa di diverso da un semplice SELECT: OPTION (RECOMPILE)
    • Crea una tabella temporanea locale (cioè singola #) e copia il contenuto del TVP nella tabella temporanea

4

Penso che continuerei a seguire un approccio di inserimento di massa. Potresti scoprire che tempdb viene ancora colpito utilizzando un TVP con un numero ragionevole di righe. Questa è la mia sensazione viscerale, non posso dire di aver testato le prestazioni dell'uso di TVP (sono interessato anche a sentire gli input degli altri)

Non si menziona se si utilizza .NET, ma l'approccio che ho adottato per ottimizzare le soluzioni precedenti è stato quello di eseguire un caricamento di massa dei dati utilizzando la classe SqlBulkCopy : non è necessario prima scrivere i dati su un file caricamento, basta dare alla classe SqlBulkCopy (ad esempio) un DataTable - questo è il modo più veloce per inserire dati nel DB. 5-10.000 righe non sono molte, l'ho usato per un massimo di 750.000 righe. Sospetto che in generale, con poche centinaia di righe, non farebbe una grande differenza usando un TVP. Ma il ridimensionamento sarebbe limitato IMHO.

Forse la nuova funzionalità MERGE in SQL 2008 potrebbe avvantaggiarti?

Inoltre, se la tua tabella di staging esistente è una singola tabella che viene utilizzata per ogni istanza di questo processo e sei preoccupato per contese, ecc., Hai considerato la creazione di una nuova tabella di staging "temporanea" ma fisica ogni volta, quindi rilasciarla quando è finito con?

Nota che puoi ottimizzare il caricamento in questa tabella di staging, popolandola senza alcun indice. Quindi, una volta popolato, aggiungi gli eventuali indici richiesti a quel punto (FILLFACTOR = 100 per prestazioni di lettura ottimali, poiché a questo punto non verrà aggiornato).


Io uso .NET e il processo sembra essere precedente SqlBulkCopye semplicemente non è mai stato modificato. Grazie per avermelo ricordato, potrebbe valere la pena rivisitare. MERGEè anche già ampiamente utilizzato e le tabelle temporanee sono state provate una volta in precedenza, ma si sono rivelate più lente e difficili da gestire. Grazie per l'input!
Aaronaught

-2

I tavoli di staging sono buoni! Davvero non vorrei farlo in nessun altro modo. Perché? Perché le importazioni di dati possono cambiare in modo imprevisto (e spesso in modi che non puoi prevedere, come l'ora in cui le colonne erano ancora chiamate nome e cognome ma avevano i dati del nome nella colonna del cognome, ad esempio, per scegliere un esempio non a caso.) Facile ricerca del problema con una tabella di staging in modo da poter vedere esattamente quali dati erano nelle colonne gestite dall'importazione. Più difficile da trovare penso quando usi una tabella di memoria. Conosco molte persone che si guadagnano da vivere come me e tutte consigliano di utilizzare tabelle di staging. Sospetto che ci sia una ragione per questo.

L'ulteriore correzione di una piccola modifica dello schema in un processo di lavoro è più semplice e richiede meno tempo rispetto alla riprogettazione del processo. Se funziona e nessuno è disposto a pagare ore per cambiarlo, correggi solo ciò che deve essere corretto a causa della modifica dello schema. Modificando l'intero processo, introduci molti più potenziali nuovi bug che apportando una piccola modifica a un processo di lavoro esistente e testato.

E come farai a farla finita con tutte le attività di pulizia dei dati? Potresti eseguirli in modo diverso, ma devono ancora essere fatti. Di nuovo, cambiare il processo nel modo in cui lo descrivi è molto rischioso.

Personalmente mi sembra che tu sia offeso usando tecniche più vecchie piuttosto che avere la possibilità di giocare con nuovi giocattoli. Sembra che tu non abbia alcuna base reale per voler cambiare diverso dall'inserimento di massa è così 2000.


27
SQL 2008 esiste da 2 anni e questo processo esiste da anni, e questa è la prima volta che ho persino pensato di cambiarlo. Il commento sarcastico alla fine era davvero necessario?
Aaronaught,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.