In Bezug auf die Methodik glaube ich, dass Sie den falschen B-Baum bellen ;-).
Was wir wissen:
Lassen Sie uns zunächst konsolidieren und überprüfen, was wir über die Situation wissen:
Was wir vermuten können:
Als Nächstes können wir uns alle diese Datenpunkte zusammen ansehen, um herauszufinden, ob wir zusätzliche Details synthetisieren können, die uns helfen, einen oder mehrere Engpässe zu finden, und entweder auf eine Lösung hinweisen oder zumindest einige mögliche Lösungen ausschließen.
Die derzeitige Denkrichtung in den Kommentaren ist, dass das Hauptproblem die Datenübertragung zwischen SQL Server und Excel ist. Ist das wirklich der Fall? Wenn die gespeicherte Prozedur für jede der 800.000 Zeilen aufgerufen wird und für jeden Aufruf (dh für jede Zeile) 50 ms benötigt, summiert sich dies zu 40.000 Sekunden (nicht ms). Und das entspricht 666 Minuten (hhmm ;-) oder etwas mehr als 11 Stunden. Es wurde jedoch gesagt, dass der gesamte Prozess nur 7 Stunden in Anspruch nimmt. Wir sind bereits 4 Stunden über die gesamte Zeit, und wir haben sogar rechtzeitig hinzugefügt, um die Berechnungen durchzuführen oder die Ergebnisse wieder in SQL Server zu speichern. Also stimmt hier etwas nicht.
Bei der Definition der gespeicherten Prozedur gibt es nur einen Eingabeparameter für @FileID
; Es ist kein Filter aktiviert @RowID
. Ich vermute also, dass eines der folgenden beiden Szenarien eintritt:
- Diese gespeicherte Prozedur wird nicht für jede Zeile aufgerufen, sondern für jede Zeile
@FileID
, die sich anscheinend über ungefähr 4000 Zeilen erstreckt. Wenn die angegebenen 4000 zurückgegebenen Zeilen eine ziemlich konsistente Menge sind, gibt es nur 200 dieser Gruppen in den 800.000 Zeilen. Und 200 Ausführungen, die jeweils 50 ms dauern, sind in diesen 7 Stunden nur 10 Sekunden.
- Wenn diese gespeicherte Prozedur tatsächlich für jede Zeile aufgerufen wird, dauert es nicht
@FileID
etwas länger, bis beim ersten Übergeben einer neuen Zeile neue Zeilen in den Pufferpool gezogen werden. Die nächsten 3999 Ausführungen werden jedoch in der Regel schneller zurückgegeben, da sie bereits vorhanden sind zwischengespeichert, richtig?
Ich denke, dass die Konzentration auf diese gespeicherte Prozedur "Filter" oder jede Datenübertragung von SQL Server nach Excel ein roter Faden ist .
Im Moment sind meines Erachtens die wichtigsten Indikatoren für eine schwache Leistung:
- Es gibt 800.000 Zeilen
- Die Operation wird zeilenweise ausgeführt
- Die Daten werden zurück in SQL Server gespeichert, daher "[verwendet] Werte aus einigen Spalten , um andere Spalten zu manipulieren " [mein em phas ist ;-)]
Ich vermute, dass:
- Zwar gibt es noch Raum für Verbesserungen beim Abrufen und Berechnen von Daten, doch würde eine Verbesserung der Daten nicht zu einer signifikanten Reduzierung der Verarbeitungszeit führen.
- Der größte Engpass besteht darin, 800.000 separate
UPDATE
Kontoauszüge auszustellen , das sind 800.000 separate Transaktionen.
Meine Empfehlung (basierend auf aktuell verfügbaren Informationen):
Ihr größter Verbesserungsbereich besteht darin, mehrere Zeilen gleichzeitig zu aktualisieren (dh in einer Transaktion). Sie sollten Ihren Prozess Arbeit in Bezug auf jede Aktualisierung FileID
statt jedem RowID
. So:
- Lesen Sie alle 4000 Zeilen eines bestimmten
FileID
in ein Array
- Das Array sollte Elemente enthalten, die die zu bearbeitenden Felder darstellen
- Durchlaufen Sie das Array und bearbeiten Sie jede Zeile wie bisher
- Sobald alle Zeilen im Array (dh für diese bestimmte
FileID
) berechnet wurden:
- Starten Sie eine Transaktion
- Rufen Sie jedes Update für jedes auf
RowID
- Wenn keine Fehler vorliegen, wird die Transaktion festgeschrieben
- Wenn ein Fehler aufgetreten ist, führen Sie einen Rollback durch und behandeln Sie ihn entsprechend
Wenn Ihr Clustered-Index noch nicht als definiert (FileID, RowID)
ist, sollten Sie dies berücksichtigen (wie @MikaelEriksson in einem Kommentar zur Frage vorgeschlagen hat). Es hilft diesen Singleton-UPDATEs nicht, aber es würde die Aggregatoperationen zumindest geringfügig verbessern, z. B. was Sie in dieser gespeicherten "Filter" -Prozedur tun, da sie alle auf basieren FileID
.
Sie sollten erwägen, die Logik in eine kompilierte Sprache zu verschieben. Ich würde vorschlagen, eine .NET WinForms-App oder sogar eine Konsolen-App zu erstellen. Ich bevorzuge die Konsolen-App, da das Planen über SQL Agent oder Windows Scheduled Tasks einfach ist. Es sollte egal sein, ob es in VB.NET oder C # gemacht wird. VB.NET passt möglicherweise besser zu Ihrem Entwickler, es wird jedoch noch eine gewisse Lernkurve geben.
Ich sehe derzeit keinen Grund, zu SQLCLR zu wechseln. Wenn sich der Algorithmus häufig ändert, wird es ärgerlich, die Assembly die ganze Zeit neu bereitzustellen. Das erneute Erstellen einer Konsolen-App und das Ablegen der EXE-Datei im richtigen freigegebenen Ordner im Netzwerk, sodass Sie nur dasselbe Programm ausführen und es zufällig immer auf dem neuesten Stand ist, sollte relativ einfach sein.
Ich denke nicht, dass es hilfreich wäre, die Verarbeitung vollständig in T-SQL zu verlagern, wenn das Problem meines Erachtens darin besteht, dass Sie jeweils nur ein UPDATE ausführen.
Wenn die Verarbeitung in .NET verschoben wird, können Sie dann TVPs (Table-Valued Parameters) verwenden, sodass Sie das Array an eine gespeicherte Prozedur übergeben, die eine UPDATE
JOIN-Anweisung für die TVP-Tabellenvariable aufruft und somit eine einzelne Transaktion ist . Der TVP sollte schneller sein als 4000 INSERT
s, die in einer einzigen Transaktion zusammengefasst sind. Der Gewinn durch die Verwendung von TVPs mit mehr als 4000 INSERT
Sekunden in einer Transaktion ist jedoch wahrscheinlich nicht so hoch wie die Verbesserung, die beim Übergang von 800.000 separaten Transaktionen auf nur 200 Transaktionen mit jeweils 4000 Zeilen zu verzeichnen ist.
Die TVP-Option ist für die VBA-Seite nicht von Haus aus verfügbar, aber jemand hat sich eine Lösung ausgedacht, die möglicherweise einen Test wert ist:
Wie kann ich die Datenbankleistung beim Wechsel von VBA zu SQL Server 2008 R2 verbessern?
WENN der Filter-Proc nur FileID
in der WHERE
Klausel verwendet wird und wenn dieser Proc wirklich für jede Zeile aufgerufen wird, können Sie Verarbeitungszeit sparen, indem Sie die Ergebnisse des ersten Laufs zwischenspeichern und für die restlichen Zeilen verwenden FileID
. richtig?
Sobald Sie die Verarbeitung getan pro FileID , dann können wir reden über eine parallele Verarbeitung beginnen. Aber das ist zu diesem Zeitpunkt vielleicht nicht nötig :). Angesichts der Tatsache, dass es sich um drei nicht ideale Hauptteile handelt: Excel-, VBA- und 800k-Transaktionen, ist jede Rede von SSIS oder Parallelogrammen oder wer weiß was, vorzeitige Optimierung / vor dem Pferd liegendes Zeug . Wenn wir diesen 7-stündigen Prozess auf 10 Minuten oder weniger reduzieren können, würden Sie dann noch über zusätzliche Möglichkeiten nachdenken, um ihn schneller zu machen? Gibt es eine Zielerfüllungszeit, die Sie im Auge haben? Denken Sie daran, dass die Verarbeitung einmal pro FileID erfolgt Wenn Sie eine VB.NET-Konsolenanwendung (dh eine Befehlszeilen-EXE-Datei) hätten, würde Sie nichts daran hindern, einige dieser Datei-IDs gleichzeitig auszuführen :), sei es über den SQL Agent-CmdExec-Schritt oder über Windows Scheduled Tasks. etc.
UND Sie können immer einen "schrittweisen" Ansatz wählen und gleichzeitig einige Verbesserungen vornehmen. Beginnen Sie beispielsweise mit den Aktualisierungen pro FileID
und verwenden Sie daher eine Transaktion für diese Gruppe. Versuchen Sie dann, den TVP zum Laufen zu bringen. Dann lesen Sie, wie Sie diesen Code nehmen und nach VB.NET verschieben (und TVPs funktionieren in .NET, so dass eine gute Portierung möglich ist).
Was wir nicht wissen, das könnte noch helfen:
- Wird die gespeicherte Prozedur "filter" pro RowID oder pro FileID ausgeführt ? Haben wir überhaupt die vollständige Definition dieser gespeicherten Prozedur?
- Vollständiges Schema der Tabelle. Wie breit ist dieser Tisch? Wie viele Felder mit variabler Länge gibt es? Wie viele Felder sind NULL-fähig? Falls NULL-Werte vorhanden sind, wie viele enthalten NULL-Werte?
- Indizes für diese Tabelle. Ist es partitioniert? Wird entweder ROW- oder PAGE-Komprimierung verwendet?
- Wie groß ist diese Tabelle in MB / GB?
- Wie wird die Indexpflege für diese Tabelle gehandhabt? Wie fragmentiert sind die Indizes? Wie aktuell sind die Statistiken?
- Schreiben andere Prozesse in diese Tabelle, während dieser 7-stündige Prozess stattfindet? Mögliche Streitquelle.
- Lesen andere Prozesse aus dieser Tabelle, während dieser 7-stündige Prozess stattfindet? Mögliche Streitquelle.
UPDATE 1:
** Es scheint einige Verwirrung darüber zu geben, was VBA (Visual Basic für Applikationen) und was damit gemacht werden kann. Dies soll nur sicherstellen, dass wir uns alle auf derselben Webseite befinden:
UPDATE 2:
Ein weiterer zu berücksichtigender Punkt: Wie werden Verbindungen behandelt? Öffnet und schließt der VBA-Code die Verbindung für jeden Vorgang oder öffnet er die Verbindung zu Beginn des Prozesses und schließt sie am Ende des Prozesses (dh 7 Stunden später)? Selbst mit dem Verbindungspooling (das standardmäßig für ADO aktiviert sein sollte) sollte es immer noch erhebliche Auswirkungen zwischen einmaligem Öffnen und Schließen geben, anstatt 800.200- oder 1.600.000-maliges Öffnen und Schließen. Diese Werte basieren auf mindestens 800.000 UPDATEs plus entweder 200 oder 800.000 EXECs (abhängig davon, wie oft die gespeicherte Filterprozedur tatsächlich ausgeführt wird).
Dieses Problem mit zu vielen Verbindungen wird durch die oben beschriebene Empfehlung automatisch gemildert. Indem Sie eine Transaktion erstellen und alle UPDATES innerhalb dieser Transaktion ausführen, halten Sie diese Verbindung offen und verwenden sie für jede Transaktion erneut UPDATE
. Ob die Verbindung vom ersten Aufruf an offen gehalten wird, um die 4000 Zeilen pro angegebenem FileID
Wert abzurufen, oder nach diesem Vorgang "get" geschlossen und erneut für die UPDATEs geöffnet wird, hat weitaus weniger Auswirkungen, da wir jetzt über einen Unterschied von beidem sprechen Insgesamt 200 oder 400 Verbindungen über den gesamten Prozess.
UPDATE 3:
Ich habe ein paar schnelle Tests gemacht. Bitte beachten Sie, dass dies ein eher kleiner Test ist und nicht genau dieselbe Operation (pure INSERT vs EXEC + UPDATE). Die zeitlichen Unterschiede in Bezug auf den Umgang mit Verbindungen und Transaktionen sind jedoch nach wie vor relevant, sodass die Informationen so hochgerechnet werden können, dass sie hier einen relativ ähnlichen Einfluss haben.
Testparameter:
- SQL Server 2012 Developer Edition (64-Bit), SP2
Tabelle:
CREATE TABLE dbo.ManyInserts
(
RowID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
InsertTime DATETIME NOT NULL DEFAULT (GETDATE()),
SomeValue BIGINT NULL
);
Betrieb:
INSERT INTO dbo.ManyInserts (SomeValue) VALUES ({LoopIndex * 12});
- Gesamtzahl der Beilagen pro Test: 10.000
- Zurücksetzen pro Test:
TRUNCATE TABLE dbo.ManyInserts;
(Angesichts der Art dieses Tests schienen FREEPROCCACHE, FREESYSTEMCACHE und DROPCLEANBUFFERS keinen großen Mehrwert zu bieten.)
- Wiederherstellungsmodell: EINFACH (und möglicherweise 1 GB frei in der Protokolldatei)
- Tests, die Transaktionen verwenden, verwenden nur eine einzelne Verbindung, unabhängig von der Anzahl der Transaktionen.
Ergebnisse:
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
Selbst wenn die ADO-Verbindung zur Datenbank bereits für alle Vorgänge freigegeben ist, wird die Gruppierung in Batches mithilfe einer expliziten Transaktion (das ADO-Objekt sollte in der Lage sein, dies zu handhaben) mit Sicherheit erheblich verbessert (dh um mehr als das Doppelte). Reduzieren Sie die Gesamtprozesszeit.