Per quanto riguarda la metodologia, credo che abbai il b-tree sbagliato ;-).
Quello che sappiamo:
Innanzitutto, consolidiamo e rivediamo ciò che sappiamo della situazione:
Cosa possiamo supporre:
Successivamente, possiamo esaminare tutti questi punti di dati insieme per vedere se siamo in grado di sintetizzare dettagli aggiuntivi che ci aiuteranno a trovare uno o più colli di bottiglia, o puntare verso una soluzione, o almeno escludere alcune possibili soluzioni.
L'attuale direzione del pensiero nei commenti è che il problema principale è il trasferimento di dati tra SQL Server ed Excel. È davvero così? Se la procedura memorizzata viene chiamata per ciascuna delle 800.000 righe e richiede 50 ms per ogni chiamata (ovvero per ogni riga), ciò aggiunge fino a 40.000 secondi (non ms). E questo equivale a 666 minuti (hhmm ;-), o poco più di 11 ore. Eppure si diceva che l'intero processo richiedesse solo 7 ore per essere eseguito. Abbiamo già trascorso 4 ore nel tempo totale e abbiamo persino aggiunto il tempo necessario per eseguire i calcoli o salvare i risultati in SQL Server. Quindi qualcosa non è qui.
Guardando la definizione della Stored Procedure, c'è solo un parametro di input per @FileID
; non c'è alcun filtro attivo @RowID
. Quindi sospetto che stia accadendo uno dei seguenti due scenari:
- Questa procedura memorizzata non viene effettivamente chiamata per ogni riga, ma per ciascuna
@FileID
, che sembra estendersi per circa 4000 righe. Se le 4000 righe dichiarate restituite sono un importo abbastanza coerente, allora ci sono solo 200 di quelle raggruppate nelle 800.000 righe. E 200 esecuzioni che richiedono 50 ms ciascuna equivalgono a soli 10 secondi su quelle 7 ore.
- Se questa procedura memorizzata viene effettivamente chiamata per ogni riga, la prima volta che una nuova
@FileID
viene passata non richiederebbe un po 'più di tempo per estrarre nuove righe nel pool di buffer, ma le successive 3999 esecuzioni in genere tornerebbero più velocemente a causa di essere già cache, vero?
Penso che concentrarsi su questo "filtro" Stored Procedure, o su qualsiasi trasferimento di dati da SQL Server a Excel, sia un'aringa rossa .
Per il momento, penso che gli indicatori più rilevanti delle prestazioni scarse siano:
- Ci sono 800.000 righe
- L'operazione funziona su una riga alla volta
- I dati vengono salvati su SQL Server, quindi "[usa] i valori di alcune colonne per manipolare altre colonne " [la mia fase è ;-)]
Sospetto che:
- mentre esiste un certo margine di miglioramento per il recupero e i calcoli dei dati, rendere questi migliori non equivarrebbe a una riduzione significativa dei tempi di elaborazione.
- il principale collo di bottiglia sta emettendo 800.000
UPDATE
dichiarazioni separate , ovvero 800.000 transazioni separate.
La mia raccomandazione (basata sulle informazioni attualmente disponibili):
La tua più grande area di miglioramento sarebbe quella di aggiornare più righe contemporaneamente (cioè in una transazione). È necessario aggiornare il processo affinché funzioni in termini di ciascuno FileID
anziché di ciascuno RowID
. Così:
- leggi in tutte le 4000 righe di un particolare
FileID
in un array
- l'array deve contenere elementi che rappresentano i campi manipolati
- scorrere l'array, elaborando ogni riga come si fa attualmente
- una volta
FileID
calcolate tutte le righe dell'array (ovvero per questo particolare ):
- avviare una transazione
- chiama ogni aggiornamento per ciascuno
RowID
- se non ci sono errori, eseguire il commit della transazione
- se si è verificato un errore, eseguire il rollback e gestirlo in modo appropriato
Se il tuo indice cluster non è già stato definito come (FileID, RowID)
allora, dovresti considerarlo (come suggerito da @MikaelEriksson in un commento sulla domanda). Non aiuterà questi AGGIORNAMENTI singleton, ma migliorerebbe almeno leggermente le operazioni di aggregazione, come ad esempio ciò che si sta facendo in quella procedura memorizzata "filtro" poiché sono tutti basati su FileID
.
Dovresti considerare di spostare la logica in un linguaggio compilato. Suggerirei di creare un'app .NET WinForms o persino un'app console. Preferisco l'app console in quanto è facile pianificare tramite SQL Agent o le attività pianificate di Windows. Non dovrebbe importare se viene eseguito in VB.NET o C #. VB.NET potrebbe essere più adatto per il tuo sviluppatore, ma ci sarà ancora qualche curva di apprendimento.
Non vedo alcun motivo a questo punto per passare a SQLCLR. Se l'algoritmo cambia frequentemente, ciò potrebbe diventare fastidioso ridistribuire l'Assemblea per tutto il tempo. Ricostruire un'app console e far sì che .exe venga collocato nella cartella condivisa appropriata sulla rete in modo tale da eseguire lo stesso programma e che sia sempre aggiornato, dovrebbe essere abbastanza facile da fare.
Non penso che spostare completamente l'elaborazione in T-SQL sarebbe di aiuto se il problema è quello che sospetto e stai facendo un AGGIORNAMENTO alla volta.
Se l'elaborazione viene spostata in .NET, è quindi possibile utilizzare parametri a valori di tabella (TVP) in modo tale da passare l'array in una Stored Procedure che chiamerebbe un UPDATE
JOIN alla variabile della tabella TVP ed è quindi una singola transazione . Il TVP dovrebbe essere più veloce di 4000 INSERT
s raggruppati in un'unica transazione. Ma il guadagno derivante dall'utilizzo di TVP oltre 4000 INSERT
s in 1 transazione probabilmente non sarà significativo quanto il miglioramento visto passando da 800.000 transazioni separate a solo 200 transazioni di 4000 righe ciascuna.
L'opzione TVP non è disponibile in modo nativo per il lato VBA, ma qualcuno ha escogitato una soluzione alternativa che potrebbe valere la pena testare:
Come posso migliorare le prestazioni del database passando da VBA a SQL Server 2008 R2?
SE il proc del filtro sta usando solo FileID
nella WHERE
clausola e SE quel proc viene realmente chiamato per ogni riga, puoi risparmiare un po 'di tempo di elaborazione memorizzando nella cache i risultati della prima esecuzione e utilizzandoli per il resto delle righe FileID
, giusto?
Una volta che il trattamento è fatto per FileID , allora si può iniziare a parlare di elaborazione parallela. Ma potrebbe non essere necessario a quel punto :). Dato che hai a che fare con 3 parti non ideali piuttosto importanti: transazioni Excel, VBA e 800k, qualsiasi discorso su SSIS o parallelogrammi, o chissà cosa, si tratta di ottimizzazione prematura / roba di tipo carrello prima del cavallo . Se riusciamo a ottenere questo processo di 7 ore fino a 10 minuti o meno, continueresti a pensare ad altri modi per renderlo più veloce? C'è un tempo di completamento target che hai in mente? Tenere presente che una volta eseguita l'elaborazione su un ID file di base, se avessi un'app console VB.NET (cioè .EXE da riga di comando), non ci sarebbe nulla che ti impedisca di eseguire alcuni di questi FileID alla volta :), sia tramite il passaggio CmdExec di SQL Agent che le attività pianificate di Windows, eccetera.
E, puoi sempre adottare un approccio "graduale" e apportare alcuni miglioramenti alla volta. Ad esempio iniziare con l'esecuzione degli aggiornamenti per FileID
e quindi l'utilizzo di una transazione per quel gruppo. Quindi, vedi se riesci a far funzionare TVP. Quindi vedi come prendere quel codice e spostarlo su VB.NET (e i TVP funzionano in .NET, quindi eseguiranno il port).
Quello che non sappiamo che potrebbe ancora aiutare:
- La "stored procedure" filtro viene eseguita per RowID o per FileID ? Abbiamo persino la definizione completa di quella Stored Procedure?
- Schema completo della tabella. Quanto è grande questa tabella? Quanti campi di lunghezza variabile ci sono? Quanti campi sono NULLable? Se ce ne sono NULLable, quanti contengono NULL?
- Indici per questa tabella. È partizionato? Viene utilizzata la compressione ROW o PAGE?
- Quanto è grande questa tabella in termini di MB / GB?
- Come viene gestita la manutenzione dell'indice per questa tabella? Quanto sono frammentati gli indici? Come sono aggiornate le statistiche?
- Altri processi scrivono in questa tabella mentre è in corso questo processo di 7 ore? Possibile fonte di contesa.
- Altri processi vengono letti da questa tabella mentre è in corso questo processo di 7 ore? Possibile fonte di contesa.
AGGIORNAMENTO 1:
** Sembra esserci un po 'di confusione su cosa VBA (Visual Basic, Applications Edition) e cosa si può fare con esso, quindi questo è solo per assicurarsi che siamo tutti sulla stessa pagina web:
AGGIORNAMENTO 2:
Un altro punto da considerare: come vengono gestite le connessioni? Il codice VBA apre e chiude la connessione per ogni operazione o apre la connessione all'inizio del processo e la chiude alla fine del processo (ovvero 7 ore dopo)? Anche con il pool di connessioni (che, per impostazione predefinita, dovrebbe essere abilitato per ADO), dovrebbe esserci comunque un notevole impatto tra l'apertura e la chiusura una volta anziché l'apertura e la chiusura di 800.200 o 1.600.000 volte. Tali valori si basano su almeno 800.000 AGGIORNAMENTI più 200 o 800k EXEC (a seconda della frequenza con cui viene effettivamente eseguita la stored procedure di filtro).
Questo problema di troppe connessioni è mitigato automaticamente dalla raccomandazione che ho delineato sopra. Creando una transazione ed eseguendo tutti gli AGGIORNAMENTI all'interno di quella transazione, manterrai aperta quella connessione e la riutilizzerai per ciascuna UPDATE
. Il fatto che la connessione sia mantenuta aperta dalla chiamata iniziale per ottenere le 4000 righe per l'operazione specificata FileID
, o chiusa dopo tale operazione "get" e riaperta per gli AGGIORNAMENTI, ha un impatto molto minore poiché stiamo parlando di una differenza di 200 o 400 connessioni totali attraverso l'intero processo.
AGGIORNAMENTO 3:
Ho fatto alcuni test rapidi. Si prega di tenere presente che si tratta di un test su scala piuttosto ridotta e non della stessa identica operazione (INSERT puro vs EXEC + AGGIORNAMENTO). Tuttavia, le differenze nei tempi relative al modo in cui vengono gestite le connessioni e le transazioni sono ancora rilevanti, quindi le informazioni possono essere estrapolate per avere un impatto relativamente simile qui.
Parametri di prova:
- SQL Server 2012 Developer Edition (64 bit), SP2
Tavolo:
CREATE TABLE dbo.ManyInserts
(
RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
SomeValue BIGINT NULL
);
Funzionamento:
INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
- Inserti totali per ciascun test: 10.000
- Ripristini per ogni test:
TRUNCATE TABLE dbo.ManyInserts;
(data la natura di questo test, eseguire FREEPROCCACHE, FREESYSTEMCACHE e DROPCLEANBUFFERS non sembra aggiungere molto valore.)
- Modello di recupero: SEMPLICE (e forse 1 GB gratuito nel file di registro)
- I test che utilizzano Transazioni utilizzano solo una singola connessione indipendentemente da quante Transazioni.
risultati:
Test Milliseconds
------- ------------
10k INSERTs across 10k Connections 3968 - 4163
10k INSERTs across 1 Connection 3466 - 3654
10k INSERTs across 1 Transaction 1074 - 1086
10k INSERTs across 10 Transactions 1095 - 1169
Come puoi vedere, anche se la connessione ADO al DB è già condivisa tra tutte le operazioni, il raggruppamento in batch utilizzando una transazione esplicita (l'oggetto ADO dovrebbe essere in grado di gestirlo) è garantito in modo significativo (cioè con un miglioramento di 2x) ridurre il tempo complessivo del processo.