Ich habe mehrfach über die Verwendung von Cursorn geschrieben und darüber, dass es in den meisten Fällen effizienter ist, Ihre Cursor mit mengenbasierter Logik neu zu schreiben.
Ich bin aber realistisch.
Ich weiß, dass es Fälle gibt, in denen Cursor „erforderlich“ sind – Sie müssen für jede Zeile eine andere gespeicherte Prozedur aufrufen oder eine E-Mail senden, Sie führen Wartungsaufgaben für jede Datenbank durch oder Sie führen so einfach eine einmalige Aufgabe aus ist es nicht wert, die Zeit zu investieren, um auf Set-basiert umzustellen.
Wie Sie es (wahrscheinlich) heute machen
Unabhängig davon, warum Sie immer noch Cursor verwenden, sollten Sie zumindest darauf achten, nicht die recht teuren Standardoptionen zu verwenden. Die meisten Leute starten ihren Cursor so:
DECLARE c CURSOR FOR SELECT whatever FROM ...
Nun noch einmal, für Ad-hoc-, einmalige Aufgaben ist dies wahrscheinlich in Ordnung. Aber es gibt…
Andere Möglichkeiten
Ich wollte einige Tests mit den Standardeinstellungen durchführen und sie mit verschiedenen Cursoroptionen wie LOCAL
vergleichen , STATIC
, READ_ONLY
und FAST_FORWARD
. (Es gibt eine Menge Optionen, aber dies sind die am häufigsten verwendeten, da sie auf die gängigsten Arten von Cursoroperationen anwendbar sind.) Ich wollte nicht nur die Rohgeschwindigkeit einiger verschiedener Kombinationen testen, sondern auch auch die Auswirkungen auf tempdb und Speicher, sowohl nach einem kalten Dienstneustart als auch mit einem warmen Cache.
Die Abfrage, die ich dem Cursor zuführen wollte, ist eine sehr einfache Abfrage von sys.objects
, in der AdventureWorks2012-Beispieldatenbank. Dies gibt 318.500 Zeilen auf meinem System zurück (ein sehr bescheidenes 2-Core-System mit 4 GB RAM):
SELECT c1.[object_id] FROM sys.objects AS c1 CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2;
Dann habe ich diese Abfrage in einen Cursor mit verschiedenen Optionen (einschließlich der Standardeinstellungen) gepackt und einige Tests durchgeführt, wobei ich den gesamten Serverspeicher und die tempdb zugewiesenen Seiten (gemäß sys.dm_db_task_space_usage
) gemessen habe und/oder sys.dm_db_session_space_usage
) und Gesamtdauer. Ich habe auch versucht, tempdb-Konflikte mit Hilfe von Skripten von Glenn Berry und Robert Davis zu beobachten, aber auf meinem dürftigen System konnte ich keinerlei Konflikte feststellen. Natürlich bin ich auch auf SSD und absolut nichts anderes läuft auf dem System, also könnten dies Dinge sein, die Sie zu Ihren eigenen Tests hinzufügen möchten, wenn tempdb eher ein Engpass ist.
Am Ende sahen die Abfragen also etwa so aus, wobei an geeigneten Stellen diagnostische Abfragen eingestreut wurden:
DECLARE @i INT = 1; DECLARE c CURSOR -- LOCAL -- LOCAL STATIC -- LOCAL FAST_FORWARD -- LOCAL STATIC READ_ONLY FORWARD_ONLY FOR SELECT c1.[object_id] FROM sys.objects AS c1 CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2 ORDER BY c1.[object_id]; OPEN c; FETCH c INTO @i; WHILE (@@FETCH_STATUS = 0) BEGIN SET @i += 1; -- meaningless operation FETCH c INTO @i; END CLOSE c; DEALLOCATE c;
Ergebnisse
Dauer
Das wohl wichtigste und gebräuchlichste Maß ist:"Wie lange hat es gedauert?" Nun, es dauerte fast fünfmal so lange, einen Cursor mit den Standardoptionen auszuführen (oder nur mit LOCAL
angegeben), verglichen mit der Angabe von entweder STATIC
oder FAST_FORWARD
:
Erinnerung
Ich wollte auch den zusätzlichen Arbeitsspeicher messen, den SQL Server beim Erfüllen der einzelnen Cursortypen anfordern würde. Also habe ich einfach vor jedem Cold-Cache-Test neu gestartet und den Leistungsindikator Total Server Memory (KB)
gemessen vor und nach jeder Prüfung. Die beste Kombination war hier LOCAL FAST_FORWARD
:
tempdb-Nutzung
Dieses Ergebnis war für mich überraschend. Da die Definition eines statischen Cursors bedeutet, dass das gesamte Ergebnis in tempdb kopiert wird, wird es tatsächlich in sys.dm_exec_cursors
ausgedrückt als SNAPSHOT
, habe ich erwartet, dass der Treffer auf tempdb-Seiten bei allen statischen Varianten des Cursors höher ist. Dies war nicht der Fall; Wieder sehen wir einen ungefähr 5-fachen Treffer bei der tempdb-Nutzung mit dem Standard-Cursor und dem mit nur LOCAL
angegeben:
Schlussfolgerung
Seit Jahren betone ich, dass die folgende Option immer für Ihre Cursor angegeben werden sollte:
LOCAL STATIC READ_ONLY FORWARD_ONLY
Von diesem Punkt an empfehle ich Folgendes, bis ich die Möglichkeit habe, weitere Permutationen zu testen oder Fälle zu finden, in denen dies nicht die schnellste Option ist:
LOCAL FAST_FORWARD
(Nebenbei habe ich auch Tests durchgeführt, bei denen LOCAL
weggelassen wurde Option, und die Unterschiede waren vernachlässigbar.)
Dies gilt jedoch nicht unbedingt für *alle* Cursor. In diesem Fall spreche ich ausschließlich von Cursorn, bei denen Sie nur Daten vom Cursor lesen, nur in Vorwärtsrichtung, und Sie die zugrunde liegenden Daten nicht aktualisieren (entweder mit der Taste oder mit WHERE CURRENT OF
). Das sind Tests für einen anderen Tag.