Ich muss alle Datensätze (Guids hinzufügen) in zwei (indizierten) leeren Spalten mit 150 Tabellen aktualisieren, wobei jede Tabelle etwa 50.000 Datensätze enthält (mithilfe eines Skripts 40.000 Aktualisierungen gleichzeitig in c # erstellen und auf dem Server veröffentlichen) und genau vier vorhanden sind Säulen.
Auf meinem lokalen Computer (16 GB RAM, 500 GB Samsung 850, SQL Server 2014, Core i5) dauert es insgesamt 13 Minuten , wenn ich versuche, 10 Tabellen parallel auszuführen. Wenn ich 5 ausführe, ist der Vorgang in nur 1,7 Minuten abgeschlossen .
Ich verstehe, dass auf Festplattenebene etwas beschäftigt ist, aber ich brauche Hilfe bei der Quantifizierung dieses großen Zeitunterschieds.
Gibt es eine genaue SQL Server-DB-Ansicht, mit der ich diese Diskrepanz überprüfen kann? Gibt es eine genaue Möglichkeit, für eine bestimmte Hardware herauszufinden, wie viele Tabellenaktualisierungen ich parallel ausführen kann? (Der echte Testserver verfügt über mehr RAM und Festplatten mit 10.000 U / min).
Kann jemand auf etwas verweisen, das ich auf dem SQL Server verbessern kann, um die Zeitabläufe für die parallel laufenden 10 Tabellen zu verbessern?
Ich habe bereits versucht, die Größe des automatischen Wachstums von 10 MB auf 100 MB zu erhöhen, wodurch sich die Länge der Festplattenwarteschlange verbessert (von etwa 5 auf 0,1), aber die Gesamtzeit wird dadurch nicht so stark verringert.
Ich habe genau die gleiche Frage zum Stackoverflow gestellt, aber bisher keine hilfreichen Antworten erhalten, sodass einige oder jede Einsicht / Hilfe immens hilfreich wäre. :) :)
Antworten:
Angesichts des Codes in Ihrer Antwort würden Sie höchstwahrscheinlich die Leistung verbessern, indem Sie die folgenden zwei Änderungen vornehmen:
Starten Sie den Abfragestapel mit
BEGIN TRAN
und beenden Sie den Stapel mitCOMMIT TRAN
:Verringern Sie die Anzahl der Aktualisierungen pro Stapel auf weniger als 5000, um eine Eskalation der Sperren zu vermeiden (die im Allgemeinen bei 5000 Sperren auftritt). Versuchen Sie 4500.
Wenn Sie diese beiden Schritte ausführen, sollten Sie die enorme Anzahl von Trans-Log-Schreibvorgängen und Sperr- / Entsperrvorgängen verringern, die Sie derzeit durch Ausführen einzelner DML-Anweisungen generieren.
Beispiel:
AKTUALISIEREN
Die Frage ist etwas spärlich in den Details. Der Beispielcode wird nur in einer Antwort angezeigt .
Ein Bereich der Verwirrung besteht darin, dass in der Beschreibung die Aktualisierung von zwei Spalten erwähnt wird, der Beispielcode jedoch nur eine einzelne Spalte zeigt, die aktualisiert wird. Meine Antwort oben basierte auf dem Code, daher wird nur eine einzige Spalte angezeigt. Wenn wirklich zwei Spalten aktualisiert werden müssen, sollten beide Spalten in derselben
UPDATE
Anweisung aktualisiert werden :Ein weiteres unklares Problem ist, woher die "eindeutigen" Daten stammen. In der Frage wird erwähnt, dass die eindeutigen Werte GUIDs sind. Werden diese in der App-Ebene generiert? Kommen sie aus einer anderen Datenquelle, die die App-Schicht kennt und die Datenbank nicht? Dies ist wichtig, da es abhängig von den Antworten auf diese Fragen sinnvoll sein kann, folgende Fragen zu stellen:
Wenn "Ja" zu # 1, aber der Code, aus welchem Grund auch immer, in .NET generiert werden muss, können Sie Anweisungen verwenden
NEWID()
und generierenUPDATE
, dieBEGIN TRAN
für Zeilenbereiche funktionieren. In diesem Fall benötigen Sie das / 'COMMIT` seitdem nicht mehr Jede Anweisung kann alle 4500 Zeilen auf einmal verarbeiten:Wenn "Ja" zu Nummer 1 steht und es keinen wirklichen Grund dafür gibt, dass diese UPDATEs in .NET generiert werden, können Sie einfach Folgendes tun:
Der obige Code funktioniert nur, wenn die
ID
Werte nicht spärlich sind oder zumindest wenn die Werte keine größeren Lücken aufweisen als@BatchSize
, sodass in jeder Iteration mindestens eine Zeile aktualisiert wird. Dieser Code setzt auch voraus, dass dasID
Feld der Clustered Index ist. Diese Annahmen erscheinen angesichts des bereitgestellten Beispielcodes vernünftig.Wenn die
ID
Werte jedoch große Lücken aufweisen oder wenn dasID
Feld nicht der Clustered Index ist, können Sie nur nach Zeilen suchen, die noch keinen Wert haben:ABER , wenn "Nein" zu # 1 und die Werte aus einem guten Grund von .NET stammen, z. B. dass die eindeutigen Werte für jede
ID
bereits in einer anderen Quelle vorhanden sind, können Sie dies (über meinen ursprünglichen Vorschlag hinaus) noch beschleunigen, indem Sie angeben eine abgeleitete Tabelle:Ich glaube, dass die Anzahl der Zeilen, über die verbunden werden kann, auf
VALUES
1000 begrenzt ist. Deshalb habe ich zwei Sätze in einer expliziten Transaktion zusammengefasst. Sie können mit bis zu 4 Sätzen dieserUPDATE
s testen , um 4000 pro Transaktion auszuführen und die Grenze von 5000 Sperren zu unterschreiten.quelle
Basierend auf Ihrer eigenen Antwort sieht es so aus:
Sie aktualisieren die erste und die zweite leere Spalte in separaten Aktualisierungsanweisungen
Die leeren Spalten sind vom Datentyp varchar
Ich habe noch nicht genügend Repräsentanten auf DBA, um einen Kommentar abzugeben (ich habe ursprünglich die Version gesehen, die Sie auf Stack Overflow veröffentlicht haben), daher werde ich auf diese Annahme antworten.
In diesem Fall machen Sie möglicherweise einen häufigen Fehler bei Personen, die aus prozeduralen Sprachen zu SQL kommen: Sie müssen prozedural über SQL-Tabellen nachdenken und jede Zeile und Spalte einzeln aktualisieren.
SQL möchte , dass Sie satzbasierte Operationen ausführen , bei denen Sie SQL mitteilen, was Sie mit allen Zeilen in einer Abfrage / Anweisung tun möchten . Die SQL Server-Abfrage-Engine kann dann intern den besten Weg finden, um diese Änderung tatsächlich für alle Zeilen durchzuführen. Indem Sie Aktualisierungen zeilenweise durchführen, verhindern Sie, dass SQL Server das tut, was es am besten kann.
Es ist möglich, dass Sie sich dessen bewusst sind und die Art der Werte, die Sie aktualisieren müssen, macht zeilenweise Aktualisierungen unerlässlich, aber selbst dann, denke ich, könnten Sie beide Spalten für eine einzelne Tabellenzeile auf einmal aktualisieren und die halbieren Gesamtzahl der Aktualisierungen, die Sie durchführen müssen.
Wenn Sie ein gewisses Maß an Flexibilität hinsichtlich der eindeutigen Werte in Ihren Spalten haben, können Sie wahrscheinlich eine gesamte Tabelle mit einer einzigen SQL-Abfrage aktualisieren. Für echte GUID-Werte eine Abfrage wie folgt:
gibt Ihnen eindeutige
uniqueidentifier
Werte in jeder Zelle. NEWID wird hier dokumentiert, wenn Sie es noch nicht gesehen haben. Sie müssten diese Abfrage dann nur noch 150 Mal für die einzelnen Tabellen wiederholen, was leicht parallelisiert werden könnte. Ich würde darauf wetten, dass es auch massiv schneller ist.Wenn Sie etwas Zahlenbasiertes benötigen, können Sie eindeutige Zahlen wie die folgenden anwenden:
Obwohl ich vermute, dass Sie das nicht versuchen; und in jedem Fall, wenn Ihre
id
Spalte eine automatische Inkrementierung istint
, können Sie dies einfach direkt verwenden, anstatt einRow_Number()
Over darüber zu generieren .Wenn Sie etwas möchten, das auf inkrementierenden Zahlen basiert, aber nicht nur aus der Zahl besteht, können Sie einen Ausdruck um das herum erstellen, um das
RowNo
zu erreichen, was Sie wollen.Wo immer möglich, ist die Verwendung von satzbasierten Operationen eine absolute Notwendigkeit für eine effiziente SQL-Leistung.
quelle
Die Lösung gefunden. :) :)
Anstatt die 40K-Aktualisierungsabfragen gleichzeitig auszuführen (ich erstelle ein Aktualisierungsskript mit 40.000 Aktualisierungsanweisungen, wie im obigen Kommentar angegeben), wenn ich diese Anzahl auf die Hälfte reduziere - 20.000 Aktualisierungsabfragen auf einmal gibt es eine enorme Verbesserung - 10 Tabellen parallel dazu dauert es jetzt insgesamt 1,3 minuten - ich kann jetzt weitermachen.
Hier ist der Code, der das Update ausführt:
Jetzt wurde der Code geändert, um 20.000 gleichzeitig auszuführen.
Im Grunde wurden zuvor 10 (Threads) x 40.000 Aktualisierungsabfragen = 400.000 gleichzeitige Aktualisierungsabfragen beim ersten Ausführen und dann die restlichen 10 (Threads) x 10.000 Aktualisierungsabfragen ausgeführt, um alle 50.000 Datensätze in diesen 10 verschiedenen Typen zu aktualisieren.
Und jetzt tut es:
Ergebnis: Vorher: 13 Minuten , Nachher: 1,8 Minuten
Ich überprüfe jetzt, um die beste (schnellste!) Kombination herauszufinden, um diese 150 Tabellen mit mehreren Threads gleichzeitig zu aktualisieren. Wahrscheinlich kann ich eine höhere Anzahl von Tabellen parallel zu einer niedrigeren gleichzeitigen Aktualisierung wie 5k (von 20k) aktualisieren, aber ich werde jetzt damit beschäftigt sein, dies zu testen.
quelle
UPDATE t SET column5 = tvp.Value FROM [TestTable] AS t INNER JOIN @YourTableValuedParameter AS tvp ON t.id = tvp.id;
. (Ich denke, @ srutzkys Vorschlag, die Anzahl der Updates pro Thread unter 5k zu halten, würde immer noch gelten.)Es gibt keine magische Ansicht, die Ihnen sagt, wie viele Threads für die gegebene Hardware besser funktionieren, und wie bei allen guten Fragen lautet die Antwort "es kommt darauf an". Sie müssen berücksichtigen, dass zu diesem Zeitpunkt möglicherweise eine andere Last auftritt oder dass Ihre Abfrage in der CPU oder E / A stärker gewichtet ist. Aber was Sie tun können und wie es sich anhört, ist das Testen. Vielleicht möchten Sie auch eine andere Variable einfügen, MAXDOP.
Wenn möglich, lassen Sie in C # nur die Anzahl der Threads variabel sein (aus einer Datenbank oder einer Konfigurationsdatei lesen), dann können Sie Ihre Abfrage im laufenden Betrieb optimieren.
Obwohl es möglicherweise keine magische Ansicht gibt, können Sie wahrscheinlich die Wartezeit auf den Spids während jedes Laufs summieren, um zu sehen, wo die Wartezeiten und die Grenzen liegen.
quelle