Leistung von bcp / BULK INSERT im Vergleich zu tabellenwertigen Parametern

81

Ich muss mit dem BULK INSERTBefehl von SQL Server einen ziemlich alten Code neu schreiben, da sich das Schema geändert hat, und mir ist der Gedanke gekommen, dass ich vielleicht stattdessen über einen Wechsel zu einer gespeicherten Prozedur mit einem TVP nachdenken sollte, aber ich frage mich, welchen Effekt dies hat es könnte auf Leistung haben.

Einige Hintergrundinformationen, die erklären könnten, warum ich diese Frage stelle:

  • Die Daten kommen tatsächlich über einen Webdienst ein. Der Webdienst schreibt eine Textdatei in einen freigegebenen Ordner auf dem Datenbankserver, der wiederum eine ausführt BULK INSERT. Dieser Prozess wurde ursprünglich auf SQL Server 2000 implementiert, und zu der Zeit gab es wirklich keine andere Alternative, als ein paar hundert INSERTAnweisungen auf dem Server abzulegen, was eigentlich der ursprüngliche Prozess war und eine Leistungskatastrophe darstellte.

  • Die Daten werden in großen Mengen in eine permanente Staging-Tabelle eingefügt und dann in eine viel größere Tabelle zusammengeführt (wonach sie aus der Staging-Tabelle gelöscht werden).

  • Die einzufügende Datenmenge ist "groß", aber nicht "riesig" - normalerweise einige hundert Zeilen, in seltenen Fällen vielleicht 5-10.000 Zeilen oben. Daher mein Bauchgefühl ist , dass BULK INSERTeine nicht protokollierte Operation ist nicht machen , dass die großen Unterschied (aber natürlich bin ich nicht sicher, daher die Frage).

  • Das Einfügen ist tatsächlich Teil eines viel größeren Pipeline-Batch-Prozesses und muss viele Male hintereinander erfolgen. daher Leistung ist entscheidend.

Die Gründe, warum ich das BULK INSERTdurch ein TVP ersetzen möchte, sind:

  • Das Schreiben der Textdatei über NetBIOS kostet wahrscheinlich bereits einige Zeit und ist aus architektonischer Sicht ziemlich grausam.

  • Ich glaube, dass die Staging-Tabelle beseitigt werden kann (und sollte). Der Hauptgrund dafür ist, dass die eingefügten Daten gleichzeitig mit dem Einfügen für einige andere Aktualisierungen verwendet werden müssen und es weitaus teurer ist, die Aktualisierung aus der massiven Produktionstabelle zu versuchen, als eine fast leere Bereitstellung Tabelle. Mit einem TVP, der Parameter im Grunde ist die Zwischenspeichertabelle, ich alles tun , kann ich damit vor wollen / nach dem Haupteinsatz.

  • Ich könnte die Dupe-Überprüfung, den Bereinigungscode und den gesamten mit Masseneinfügungen verbundenen Overhead so gut wie beseitigen.

  • Sie müssen sich keine Gedanken über Sperrenkonflikte in der Staging-Tabelle oder in der Tempdb machen, wenn der Server einige dieser Transaktionen gleichzeitig erhält (wir versuchen, dies zu vermeiden, aber es passiert).

Ich werde dies natürlich profilieren, bevor ich etwas in Produktion bringe, aber ich dachte, es wäre eine gute Idee, zuerst nachzufragen, bevor ich die ganze Zeit damit verbringe, zu prüfen, ob jemand strenge Warnungen bezüglich der Verwendung von TVPs für diesen Zweck hat.

Also - für alle, die mit SQL Server 2008 vertraut genug sind, um dies zu versuchen oder zumindest zu untersuchen, wie lautet das Urteil? Schneiden Beilagen für Einsätze von beispielsweise einigen hundert bis einigen tausend Zeilen, die ziemlich häufig vorkommen, den Senf? Gibt es einen signifikanten Leistungsunterschied zu Bulk-Einsätzen?


Update: Jetzt mit 92% weniger Fragezeichen!

(AKA: Testergebnisse)

Das Endergebnis ist jetzt nach einem 36-stufigen Bereitstellungsprozess in der Produktion. Beide Lösungen wurden ausgiebig getestet:

  • Herausreißen des Codes für freigegebene Ordner und SqlBulkCopydirekte Verwendung der Klasse;
  • Wechseln zu einer gespeicherten Prozedur mit TVPs.

Damit die Leser eine Vorstellung davon bekommen, was genau getestet wurde, um Zweifel an der Zuverlässigkeit dieser Daten auszuräumen, finden Sie hier eine detailliertere Erläuterung der tatsächlichen Funktionsweise dieses Importvorgangs :

  1. Beginnen Sie mit einer zeitlichen Datensequenz, die normalerweise etwa 20-50 Datenpunkte umfasst (obwohl sie manchmal einige hundert betragen kann).

  2. Führen Sie eine ganze Reihe verrückter Verarbeitungen durch, die größtenteils unabhängig von der Datenbank sind. Dieser Prozess ist parallelisiert, so dass ungefähr 8-10 der Sequenzen in (1) gleichzeitig verarbeitet werden. Jeder parallele Prozess erzeugt 3 zusätzliche Sequenzen.

  3. Nehmen Sie alle 3 Sequenzen und die Originalsequenz und kombinieren Sie sie zu einem Stapel.

  4. Kombinieren Sie die Stapel aus allen 8-10 jetzt abgeschlossenen Verarbeitungsaufgaben zu einem großen Super-Stapel.

  5. Importieren Sie es entweder mit der BULK INSERTStrategie (siehe nächster Schritt) oder mit der TVP-Strategie (fahren Sie mit Schritt 8 fort).

  6. Verwenden Sie die SqlBulkCopyKlasse, um den gesamten Super-Batch in 4 permanente Staging-Tabellen zu speichern.

  7. Führen Sie eine gespeicherte Prozedur aus, die (a) eine Reihe von Aggregationsschritten für 2 der Tabellen einschließlich mehrerer JOINBedingungen ausführt und (b) eine MERGEfür 6 Produktionstabellen unter Verwendung der aggregierten und nicht aggregierten Daten ausführt . (Fertig)

    ODER

  8. Generieren Sie 4 DataTableObjekte mit den zusammenzuführenden Daten. 3 von ihnen enthalten CLR-Typen, die von ADO.NET-TVPs leider nicht ordnungsgemäß unterstützt werden. Daher müssen sie als Zeichenfolgendarstellungen eingefügt werden, was die Leistung etwas beeinträchtigt.

  9. Führen Sie die TVPs einer gespeicherten Prozedur zu, die im Wesentlichen dieselbe Verarbeitung wie (7) ausführt, jedoch direkt mit den empfangenen Tabellen. (Fertig)

Die Ergebnisse waren ziemlich nahe beieinander, aber der TVP-Ansatz schnitt letztendlich im Durchschnitt besser ab, selbst wenn die Daten 1000 Zeilen um einen kleinen Betrag überstiegen.

Beachten Sie, dass dieser Importvorgang viele tausend Mal hintereinander ausgeführt wird. Daher war es sehr einfach, eine durchschnittliche Zeit zu ermitteln, indem einfach gezählt wurde, wie viele Stunden (ja, Stunden) erforderlich waren, um alle Zusammenführungen abzuschließen.

Ursprünglich dauerte eine durchschnittliche Zusammenführung fast genau 8 Sekunden (unter normaler Last). Durch Entfernen des NetBIOS-Kludges und Umschalten SqlBulkCopyauf wurde die Zeit auf fast genau 7 Sekunden reduziert. Durch die Umstellung auf TVPs wurde die Zeit auf 5,2 Sekunden pro Charge weiter reduziert . Das ist eine 35% ige Verbesserung des Durchsatzes für einen Prozess, dessen Laufzeit in Stunden gemessen wird - also überhaupt nicht schlecht. Es ist auch eine Verbesserung von ~ 25% gegenüber SqlBulkCopy.

Ich bin eigentlich ziemlich zuversichtlich, dass die wahre Verbesserung deutlich mehr war. Während des Tests stellte sich heraus, dass die endgültige Zusammenführung nicht mehr der kritische Pfad war. Stattdessen begann der Webdienst, der die gesamte Datenverarbeitung durchführte, unter der Anzahl der eingehenden Anforderungen zu knicken. Weder die CPU noch die Datenbank-E / A waren wirklich voll und es gab keine signifikante Sperraktivität. In einigen Fällen wurde zwischen aufeinanderfolgenden Zusammenführungen eine Lücke von einigen Sekunden im Leerlauf angezeigt. Es gab eine leichte Lücke, die jedoch bei der Verwendung viel kleiner war (etwa eine halbe Sekunde) SqlBulkCopy. Aber ich nehme an, das wird eine Geschichte für einen anderen Tag.

Schlussfolgerung: Tabellenwertige Parameter sind BULK INSERTbei komplexen Import- und Transformationsprozessen, die mit mittelgroßen Datensätzen ausgeführt werden, tatsächlich besser als Operationen.


Ich möchte noch einen weiteren Punkt hinzufügen, um die Besorgnis einiger Leute zu lindern, die Pro-Staging-Tische sind. In gewisser Weise ist dieser gesamte Service ein riesiger Staging-Prozess. Jeder Schritt des Prozesses wird stark geprüft, sodass wir keine Staging-Tabelle benötigen, um festzustellen, warum eine bestimmte Zusammenführung fehlgeschlagen ist (obwohl dies in der Praxis fast nie der Fall ist). Alles was wir tun müssen, ist ein Debug-Flag im Service zu setzen und es wird zum Debugger brechen oder seine Daten in eine Datei anstelle der Datenbank sichern.

Mit anderen Worten, wir haben bereits mehr als genug Einblick in den Prozess und benötigen nicht die Sicherheit eines Staging-Tisches. der einzige Grund , warum ich war die Staging - Tabelle in erster Linie hatte Dreschen auf alle die zu vermeiden INSERTund UPDATEAussagen , die wir sonst verwenden gehabt hätten. Im ursprünglichen Prozess lebten die Staging-Daten ohnehin nur für Bruchteile einer Sekunde in der Staging-Tabelle, sodass sie in Bezug auf Wartung / Wartbarkeit keinen Mehrwert erbrachten.

Beachten Sie auch, dass wir nicht jede einzelne BULK INSERTOperation durch TVPs ersetzt haben. Einige Vorgänge, die sich mit größeren Datenmengen befassen und / oder mit den Daten nichts Besonderes tun müssen, als sie in die Datenbank zu werfen, werden weiterhin verwendet SqlBulkCopy. Ich behaupte nicht, dass TVPs ein Allheilmittel für die Leistung sind, sondern nur, dass sie SqlBulkCopyin diesem speziellen Fall erfolgreich waren und mehrere Transformationen zwischen der anfänglichen Inszenierung und der endgültigen Zusammenführung beinhalteten.

Da haben Sie es also. Punkt geht an TToni, um den relevantesten Link zu finden, aber ich schätze auch die anderen Antworten. Danke noch einmal!

Aaronaught
quelle
Dies ist eine erstaunliche Frage für sich, ich
denke

Antworten:

10

Ich habe nicht wirklich Erfahrung mit TVP noch, aber es ist ein schöner Leistungsvergleich INSERT Diagramm gegen MASSE in MSDN hier .

Sie sagen, dass BULK INSERT höhere Startkosten hat, danach aber schneller ist. In einem Remote-Client-Szenario ziehen sie die Linie in etwa 1000 Zeilen (für "einfache" Serverlogik). Nach ihrer Beschreibung zu urteilen, würde ich sagen, dass Sie mit der Verwendung von TVPs in Ordnung sein sollten. Der Leistungseinbruch - falls vorhanden - ist wahrscheinlich vernachlässigbar und die architektonischen Vorteile scheinen sehr gut zu sein.

Bearbeiten: Nebenbei bemerkt, Sie können die serverlokale Datei vermeiden und trotzdem eine Massenkopie verwenden, indem Sie das SqlBulkCopy-Objekt verwenden. Füllen Sie einfach eine DataTable aus und geben Sie sie in die "WriteToServer" -Methode einer SqlBulkCopy-Instanz ein. Einfach zu bedienen und sehr schnell.

TToni
quelle
Vielen Dank für den Link, der eigentlich sehr nützlich ist, da MS TVPs zu empfehlen scheint, wenn die Daten komplexe Logik speisen (was auch der Fall ist) und wir auch die Möglichkeit haben, die Stapelgröße zu erhöhen oder zu verringern, damit wir nicht zu weit über die hinausgehen 1k-reihiger Schmerzpunkt. Auf dieser Grundlage kann es sich lohnen, zumindest etwas auszuprobieren und zu sehen, auch wenn es zu langsam wird.
Aaronaught
Ja, der Link ist interessant. @Aaronaught - In solchen Situationen lohnt es sich immer, die Leistung potenzieller Ansätze zu untersuchen und zu analysieren. Ich würde mich also über Ihre Ergebnisse freuen!
AdaTheDev
7

Die Tabelle, die in Bezug auf den in der Antwort von @ TToni angegebenen Link erwähnt wird, muss im Kontext betrachtet werden. Ich bin nicht sicher, wie viel tatsächliche Forschung in diese Empfehlungen geflossen ist (beachten Sie auch, dass die Tabelle nur in den 2008und 2008 R2Versionen dieser Dokumentation verfügbar zu sein scheint ).

Auf der anderen Seite gibt es dieses Whitepaper des SQL Server-Kundenberatungsteams: Maximieren des Durchsatzes mit TVP

Ich verwende TVPs seit 2009 und habe zumindest meiner Erfahrung nach festgestellt, dass TVPs für etwas anderes als das einfache Einfügen in eine Zieltabelle ohne zusätzliche logische Anforderungen (was selten der Fall ist) in der Regel die bessere Option sind.

Ich vermeide Staging-Tabellen, da die Datenüberprüfung auf App-Ebene erfolgen sollte. Durch die Verwendung von TVPs ist dies leicht zu berücksichtigen, und die TVP-Tabellenvariable in der gespeicherten Prozedur ist naturgemäß eine lokalisierte Staging-Tabelle (daher kein Konflikt mit anderen Prozessen, die zur gleichen Zeit ausgeführt werden, wie Sie sie erhalten, wenn Sie eine echte Tabelle für das Staging verwenden ).

In Bezug auf die in der Frage durchgeführten Tests könnte gezeigt werden, dass sie noch schneller sind als ursprünglich festgestellt:

  1. Sie sollten keine DataTable verwenden, es sei denn, Ihre Anwendung verwendet sie außerhalb des Sendens der Werte an den TVP. Die Verwendung der IEnumerable<SqlDataRecord>Schnittstelle ist schneller und benötigt weniger Speicher, da Sie die Sammlung nicht nur zum Senden an die Datenbank duplizieren. Ich habe dies an folgenden Stellen dokumentiert:
  2. TVPs sind Tabellenvariablen und führen daher keine Statistiken. Das heißt, sie melden nur 1 Zeile an das Abfrageoptimierungsprogramm. Also, in deinem Prozess entweder:
    • Verwenden Sie die Neukompilierung auf Anweisungsebene für alle Abfragen, bei denen TVP für etwas anderes als ein einfaches SELECT verwendet wird: OPTION (RECOMPILE)
    • Erstellen Sie eine lokale temporäre Tabelle (dh eine einzelne #) und kopieren Sie den Inhalt des TVP in die temporäre Tabelle
Solomon Rutzky
quelle
4

Ich denke, ich würde mich immer noch an einen Bulk-Insert-Ansatz halten. Möglicherweise wird Tempdb immer noch von einem TVP mit einer angemessenen Anzahl von Zeilen getroffen. Dies ist mein Bauchgefühl. Ich kann nicht sagen, dass ich die Leistung der Verwendung von TVP getestet habe (ich bin jedoch daran interessiert, auch andere Eingaben zu hören).

Sie erwähnen nicht, ob Sie .NET verwenden, aber der Ansatz, den ich zur Optimierung früherer Lösungen gewählt habe, bestand darin, eine große Datenmenge mit der SqlBulkCopy- Klasse zu laden. Sie müssen die Daten vorher nicht in eine Datei schreiben Laden Sie der SqlBulkCopy- Klasse (z. B.) einfach eine DataTable - das ist der schnellste Weg, um Daten in die Datenbank einzufügen. 5-10K Zeilen sind nicht viel, ich habe dies für bis zu 750K Zeilen verwendet. Ich vermute, dass es mit ein paar hundert Zeilen im Allgemeinen keinen großen Unterschied machen würde, einen TVP zu verwenden. Aber eine Vergrößerung wäre meiner Meinung nach begrenzt.

Vielleicht würde Ihnen die neue MERGE- Funktionalität in SQL 2008 zugute kommen?

Wenn Ihre vorhandene Staging-Tabelle eine einzelne Tabelle ist, die für jede Instanz dieses Prozesses verwendet wird, und Sie sich Sorgen über Konflikte usw. machen, haben Sie darüber nachgedacht, jedes Mal eine neue "temporäre", aber physische Staging-Tabelle zu erstellen und diese dann zu löschen, wenn dies der Fall ist fertig mit?

Beachten Sie, dass Sie das Laden in diese Staging-Tabelle optimieren können, indem Sie sie ohne Indizes füllen. Fügen Sie dann nach dem Auffüllen alle erforderlichen Indizes zu diesem Zeitpunkt hinzu (FILLFACTOR = 100 für eine optimale Leseleistung, da es zu diesem Zeitpunkt nicht aktualisiert wird).

AdaTheDev
quelle
Ich benutze .NET, und der Prozess ist älter als SqlBulkCopyund wurde einfach nie geändert. Vielen Dank, dass Sie mich daran erinnert haben. Es könnte sich lohnen, es noch einmal zu besuchen. MERGEwird auch bereits ausgiebig verwendet, und temporäre Tabellen wurden bereits einmal ausprobiert, erwiesen sich jedoch als langsamer und schwieriger zu verwalten. Danke für die Eingabe!
Aaronaught
-2

Inszenierungstische sind gut! Wirklich, ich würde es nicht anders machen wollen. Warum? Weil sich Datenimporte unerwartet ändern können (und oft auf eine Weise, die Sie nicht vorhersehen können, wie die Zeit, als die Spalten noch Vor- und Nachname genannt wurden, aber die Vorname-Daten in der Nachname-Spalte hatten, um beispielsweise ein Beispiel nicht auszuwählen nach dem Zufallsprinzip.) Einfache Untersuchung des Problems mit einer Staging-Tabelle, sodass Sie genau sehen können, welche Daten in den vom Import behandelten Spalten enthalten waren. Ich denke, es ist schwieriger zu finden, wenn Sie eine In-Memory-Tabelle verwenden. Ich kenne viele Leute, die wie ich ihren Lebensunterhalt mit Importen verdienen, und alle empfehlen die Verwendung von Staging-Tabellen. Ich vermute, dass es dafür einen Grund gibt.

Das weitere Korrigieren einer kleinen Schemaänderung an einem Arbeitsprozess ist einfacher und weniger zeitaufwendig als das Neugestalten des Prozesses. Wenn es funktioniert und niemand bereit ist, Stunden für die Änderung zu zahlen, beheben Sie nur das, was aufgrund der Schemaänderung behoben werden muss. Indem Sie den gesamten Prozess ändern, führen Sie weitaus mehr potenzielle neue Fehler ein, als wenn Sie eine kleine Änderung an einem vorhandenen, getesteten Arbeitsprozess vornehmen.

Und wie werden Sie alle Datenbereinigungsaufgaben abschaffen? Sie können sie anders machen, aber sie müssen noch gemacht werden. Auch hier ist es sehr riskant, den von Ihnen beschriebenen Prozess zu ändern.

Persönlich klingt es für mich so, als wären Sie nur beleidigt, wenn Sie ältere Techniken anwenden, anstatt die Chance zu bekommen, mit neuen Spielsachen zu spielen. Sie scheinen keine wirkliche Grundlage zu haben, um etwas anderes ändern zu wollen, als Bulk Insert ist also 2000.

HLGEM
quelle
27
SQL 2008 wurde für 2 Jahre und dieser Prozess hat seit Ewigkeiten herum, und dies ist das erste Mal , dass ich selbst habe als es zu ändern. War der snarky Kommentar am Ende wirklich notwendig?
Aaronaught