SQL: Was verlangsamt INSERTs, wenn nicht CPU oder IO?


19

Wir haben eine Datenbank für ein Produkt, das schwer zu beschreiben ist. Wir haben gerade einen neuen Server mit einer SSD gekauft, um zu helfen. Zu unserer Überraschung waren die Einfügungen nicht schneller als auf unserer alten Maschine mit viel langsamerer Lagerung. Beim Benchmarking haben wir festgestellt, dass die E / A-Rate des SQL Server-Prozesses sehr niedrig war.

Zum Beispiel habe ich das Skript auf dieser Seite ausgeführt , mit der Ausnahme, dass ich ein BEGIN TRAN und ein COMMIT um die Schleife hinzugefügt habe. Bestenfalls konnte ich sehen, dass die Festplattenauslastung 7 MB / s erreichte, während die CPU 5% kaum berührte. Auf dem Server sind 64 GB installiert, und es wird 10 verwendet. Die Gesamtlaufzeit betrug 2 Minuten und 15 Sekunden für den ersten Aufruf bis zu einer Minute für nachfolgende Aufrufe. Die Datenbank wird einfach wiederhergestellt und war während des Tests inaktiv. Ich ließ den Tisch zwischen jedem Anruf fallen.

Warum ist so ein einfaches Skript so langsam? Die Hardware wird kaum genutzt. Sowohl dedizierte Festplatten-Benchmarking-Tools als auch SQLIO zeigen an, dass die SSD sowohl beim Lesen als auch beim Schreiben mit Geschwindigkeiten von über 500 MBit / s ordnungsgemäß funktioniert. Ich verstehe, dass zufällige Schreibvorgänge langsamer sind als sequentielle Schreibvorgänge, aber ich würde erwarten, dass eine einfache Einfügung in eine Tabelle ohne gruppierte Indizierung viel schneller ist.

Letztendlich ist unser Szenario viel komplexer, aber ich habe das Gefühl, dass ich zuerst einen einfachen Fall verstehen muss. Kurz gesagt, unsere Anwendung löscht alte Daten, kopiert neue Daten mithilfe von SqlBulkCopy in Staging-Tabellen, filtert sie und kopiert die Daten schließlich mithilfe von MERGE und / oder INSERT INTO (je nach Fall) in die endgültigen Tabellen.

-> BEARBEITEN 1: Ich habe die Prozedur von Martin Smith befolgt und folgendes Ergebnis erhalten:

[Wait Type]  [Wait Count] [Total Wait (ms)] [T. Resource Wait (ms)] [T. Signal Wait (ms)]
NETWORK_IO          5008              46735                 46587        148
LOGBUFFER           901               5994                  5977         17
PAGELATCH_UP        40                866                   865          1
SOS_SCHEDULER_YIELD 53279             219                   121          98
WRITELOG            5                 145                   145          0
PAGEIOLATCH_UP      4                 58                    58           0
LATCH_SH            5                 0                     0            0

Ich finde es seltsam, dass NETWORK_IO die meiste Zeit in Anspruch nimmt, wenn man bedenkt, dass kein Ergebnis angezeigt und keine Daten an einen anderen Ort als in die SQL-Dateien übertragen werden müssen. Umfasst der Typ NETWORK_IO alle E / A?

-> BEARBEITEN 2: Ich habe eine 20-GB-RAM-Disk erstellt und von dort aus eine Datenbank gemountet. Die beste Zeit, die ich auf der SSD hatte, war 48 Sekunden, mit der RAM-Disk ging es auf 37 Sekunden zurück. NETWORK_IO ist immer noch das größte Warten. Die maximale Schreibgeschwindigkeit auf die RAM-Disk betrug etwa 250 MB / s, während sie mehrere Gigabyte pro Sekunde verarbeiten kann. Es wurde immer noch nicht viel CPU verbraucht. Was hält also SQL davon ab?



3
das NETWORK_IOkönnte von der 3 Millionen „1 Zeile (n) betroffen“ sein , um Nachrichten zurück gesendet werden. Haben Sie versucht SET NOCOUNT ON, dem Skript etwas hinzuzufügen ?
Martin Smith

Ja, ich habe den NOCOUNT hinzugefügt.
Djof

2
Seltsam. Ich würde dann überhaupt nicht viel von der Netzwerkaktivität erwarten. Haben Sie die alten erweiterten Ereignisdateien zwischen den Läufen gelöscht? Das Skript, das sie liest, verwendet einen Platzhalter, EE_WaitStats*.xelsodass alte Ihre Ergebnisse verunreinigen.
Martin Smith

Guter Anruf, ich werde die Ergebnisse morgen aktualisieren.
Djof

Antworten:


9

Ich weiß, dass es eine alte Frage ist, aber dies könnte den Suchenden trotzdem helfen, und es ist ein Problem, das hin und wieder auftaucht.

Der Hauptgrund, warum Sie eine Leistungsobergrenze erreichen, ohne einen Ressourcenengpass zu bemerken, liegt darin, dass Sie die Grenze dessen erreicht haben, was innerhalb eines einzelnen Sitzungsthreads verarbeitet werden kann. Die Schleife wird nicht parallel verarbeitet, aber alle Einfügungen erfolgen seriell.

In meinem Fall dauert das Einfügen von 3 Millionen Zeilen 36 Sekunden. Das bedeutet 36/30000000 = 0,000012 Sekunden pro Zeile. Das geht ziemlich schnell. Auf meinem System sind lediglich 0.000012 erforderlich, um alle erforderlichen Schritte auszuführen.

Die einzige Möglichkeit, dies schneller zu erledigen, besteht darin, eine zweite Sitzung parallel zu starten.

Wenn ich 2 Sessions parallel starte, machen beide 15 Millionen Inserts. Beide sind in 18 Sekunden fertig. Ich könnte mehr skalieren, aber mein aktuelles Test-Setup erreicht bei zwei parallelen Sessions 95% CPU. Wenn ich also 3 mache, würde das die Ergebnisse verzerren, da ich auf einen CPU-Engpass stoßen würde.

Wenn ich zwei parallele Sitzungen beginne, in denen jeweils drei Millionen Zeilen eingefügt werden, werden beide in 39 Sekunden beendet. Das sind jetzt 6 Millionen Zeilen in 39 Sekunden.

Okay, das lässt uns immer noch mit der NETWORK_IO-Wartezeit auftauchen.

Die NETWORK_IO-Wartezeiten werden durch die Tatsache hinzugefügt, dass Sie erweiterte Ereignisse verwenden, um sie zu verfolgen. In meinem Fall dauert der Einsatz 36 Sekunden (im Durchschnitt). Bei Verwendung der erweiterten Ereignismethode (vom Link oben im allerersten Kommentar) wird Folgendes registriert:

Wait Type             Wait Count  Total Wait Time (ms) Total Resource Wait Time (ms) Total Signal Wait Time (ms)
NETWORK_IO            3455        68808                68802                         6
PAGEIOLATCH_SH        3           64                   64                            0
PAGEIOLATCH_UP        12          58                   58                            0
WRITE_COMPLETION      8           15                   15                            0
WRITELOG              3           9                    9                             0
PAGELATCH_UP          2           4                    4                             0
SOS_SCHEDULER_YIELD   32277       1                    0                             1
IO_COMPLETION         8           0                    0                             0
LATCH_SH              3           0                    0                             0
LOGBUFFER             1           0                    0                             0

Sie können sehen, dass 68 Sekunden von NETWORK_IO registriert sind. Da es sich bei der Insert-Schleife jedoch um eine einzelne Thread-Aktion handelt, die 36 Sekunden dauerte, kann dies nicht der Fall sein. (Ja, es werden mehrere Threads verwendet, aber die Vorgänge sind seriell, niemals parallel, sodass Sie nicht mehr Wartezeit als die Gesamtdauer der Abfrage benötigen können.)

Wenn ich keine erweiterten Ereignisse verwende, sondern nur die Wartestatistik-DMVs auf einer stillen Instanz (wobei nur ich die Einfügung ausführe), erhalte ich Folgendes:

Wait Type                   Wait Count  Total Wait Time (ms)  Total Resource Wait Time (ms) Signal Resource Wait Time (ms)
SOS_SCHEDULER_YIELD             8873                 0.21                                    0.01                                    0.20
PAGEIOLATCH_UP                  3                    0.02                                    0.02                                    0.00
PREEMPTIVE_OS_AUTHENTICATIONOPS 17                   0.02                                    0.02                                    0.00
PAGEIOLATCH_SH                  1                    0.00                                    0.00                                    0.00

Das NETWORK_IO, das Sie im erweiterten Ereignisprotokoll sehen, war also nicht mit Ihrer Einfügeschleife verknüpft. (Wenn Sie nocount nicht einschalten würden, hätten Sie massive asynchrone Netzwerk-E / A-Wartezeiten, +1 Martin)

Ich weiß jedoch nicht, warum das NETWORK_IO in der erweiterten Ereignisablaufverfolgung angezeigt wird. Sicher, das Ausschreiben der Ereignisse in eine asynchrone Datei akkumuliert ASYNC_NETWORK_IO, aber dies geschieht mit Sicherheit auf einer anderen SPID als der, nach der wir filtern. Ich könnte dies selbst als neue Frage stellen.)


1
"Sie erreichen eine Leistungsgrenze, ohne einen Ressourcenengpass zu bemerken, weil Sie die Grenze dessen erreicht haben, was innerhalb eines einzelnen Sitzungsthreads verarbeitet werden kann": Sie beschreiben einen 100% -igen CPU-Engpass (auf einem Kern). Wenn es keinen Engpass gibt, wird das System schneller laufen , sodass etwas anderes im Spiel sein muss.
Remus Rusanu

Deine Antwort ist sehr informativ, Edward. Anscheinend ist Parallelität die Lösung für unser Problem, an dem wir bereits arbeiten, obwohl Änderungen an unserem Datenbanklayout erforderlich sind. Wie Remus bin ich jedoch immer noch neugierig, warum der Computer anscheinend nicht alle (einer) CPU- oder Festplattenressourcen verbraucht hat.
Djof,

9

Typischerweise beginnen Sie , indem Sie auf sys.dm_exec_requests, die speziell auf die wait_time, wait_typeund wait_resourcefür Ihre INSERT Anfrage (n). Dies gibt einen klaren Hinweis darauf, was Ihr INSERT blockiert. Die Ergebnisse zeigen an, ob es sich um Sperrenkonflikte, Dateiwachstumsereignisse, Wartezeiten beim Löschen von Protokollen, Zuordnungskonflikte (manifestiert sich als PFS-Seitenzwischenspeicherkonflikte) usw. usw. handelt. Aktualisieren Sie Ihre Frage nach der Messung entsprechend. Ich rate Ihnen dringend, jetzt aufzuhören und die Methoden zur Fehlerbehebung bei Warteschlangen und Warteschlangen zu lesen, bevor Sie fortfahren.


3

Ich habe das Testskript auf der Seite ausgeführt, die im OP mit BEGIN TRAN / COMMIT um die Schleife verknüpft ist. Auf meinem Computer dauerte es 1:28, bis der erste Vorgang abgeschlossen war.

Dann habe ich diese beiden Befehle außerhalb der Schleife verschoben:

SELECT @Random = ROUND(((@Upper - @Lower -1) * RAND() + @Lower), 0)
SET @InsertDate = DATEADD(dd, @Random, GETDATE())

Es war in 28 Sekunden fertig.

Ich weiß nicht genau, was passiert, aber ich vermute, dass der RAND()Code eine Art Schlaf enthält, vielleicht als Teil des Algorithmus, mit dem sie Entropie erzeugen (bessere Zufallszahlen).

FWIW, SSDs sind nicht immer die beste Technologie für schreiblastige Apps. Stellen Sie für eine optimale Leistung sicher, dass sich Ihr DB-Protokoll auf einem anderen Laufwerksbuchstaben als die DB-Daten befindet, die Protokolldatei auf ihre maximale Größe vorab vergrößert ist und das Protokoll niemals abgeschnitten wird.


Vielen Dank für Ihre Eingabe RickNZ. Ich habe keine schnelleren Ergebnisse erzielt, indem ich den Code aus der Schleife verschoben habe. Warten Sie, ich habe bemerkt, dass wenn Sie es mehrmals ausführen, es schneller wird, das ist es, was Sie erlebt haben. Ich weiß, dass SSDs keine Wunderkugeln sind, aber ich habe immer noch das Gefühl, dass die Leistung nicht so ist, wie sie sein könnte.
Djof

1

Eine andere DMV, die ich verwende, um Langsamkeit zu identifizieren, ist sys.dm_os_waiting_tasks . Wenn Ihre Abfrage nicht CPU-intensiv ist, finden Sie in dieser DMV weitere Informationen zu den Wartezeiten.


0

Ich überprüfe die Liste der Warteereignisse für SQL 2008 und sehe NETWORK_IO nicht aufgelistet: http://technet.microsoft.com/en-us/library/ms179984(v=sql.100).aspx

Ich dachte, dass NETWORK_IO jetzt nur als ASYNC_NETWORK_IO aufgeführt ist, also wollte ich fragen, ob Sie Ihre SQL-Version erneut überprüfen können, weil ich einfach gespannt bin, wie / warum dieses Warteereignis für diese Version angezeigt wird.

Was die Netzwerkwartezeit angeht, so kann dies durchaus vorkommen, auch wenn Sie auf einem eigenständigen Server arbeiten. Haben Sie die Einstellungen für Ihre Netzwerkkarten überprüft? Ich frage mich, ob sie ein Problem sind.

Letztendlich sind nur wenige Ressourcenengpässe möglich: Arbeitsspeicher, CPU, Festplatten-E / A, Netzwerk und Sperren. Sie haben angegeben, dass CPU und E / A nicht das Problem sind, und Sie haben ein Warteereignis von NETWORK_IO. Ich schlage daher vor, dass Sie sich zuerst diese NIC-Karten ansehen.


1
Dies NETWORK_IOwird angezeigt, weil das OP erweiterte Ereignisse verwendet. Es wurde nie aktualisiert insys.dm_xe_map_values
Martin Smith

Ich denke das gleiche SQLRockstar, genau das, was los sein könnte. Ich habe versucht, die Netzwerkkarten vollständig zu deaktivieren. Martin wies darauf hin, dass möglicherweise noch einige alte Dateien vorhanden sind. Ich werde die Ergebnisse morgen aktualisieren, um festzustellen, ob sich etwas ändert.
Djof

Außerdem könnte es hilfreich sein, wenn wir die Ausführungspläne für die Anweisungen sehen könnten.
SQLRockstar
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.