Warum hassen Leute SQL-Cursor so sehr? [geschlossen]

127

Ich kann verstehen, dass ich aufgrund des Overheads und der Unannehmlichkeiten vermeiden möchte, einen Cursor verwenden zu müssen, aber es sieht so aus, als ob es eine ernsthafte Cursor-Phobie-Manie gibt, bei der die Leute große Anstrengungen unternehmen, um zu vermeiden, dass sie einen verwenden müssen.

In einer Frage wurde beispielsweise gefragt, wie mit einem Cursor etwas offensichtlich Triviales zu tun ist, und die akzeptierte Antwort wurde unter Verwendung einer rekursiven Abfrage mit allgemeinem Tabellenausdruck (CTE) mit einer rekursiven benutzerdefinierten Funktion vorgeschlagen, obwohl dies die Anzahl der Zeilen begrenzt, die verarbeitet werden können, auf 32 (aufgrund des rekursiven Funktionsaufruflimits im SQL Server). Dies scheint mir eine schreckliche Lösung für die Langlebigkeit des Systems zu sein, ganz zu schweigen von einer enormen Anstrengung, nur um die Verwendung eines einfachen Cursors zu vermeiden.

Was ist der Grund für diesen wahnsinnigen Hass? Hat eine "bekannte Autorität" eine Fatwa gegen Cursor erlassen? Lauert ein unaussprechliches Übel im Herzen von Cursorn, die die Moral von Kindern verfälschen oder so?

Wiki-Frage, mehr an der Antwort interessiert als an der Wiederholung.

Verwandte Informationen:

SQL Server-Schnellvorlauf-Cursor

EDIT: Lassen Sie mich genauer sein: Ich verstehe, dass Cursor nicht anstelle von normalen relationalen Operationen verwendet werden sollten ; das ist ein Kinderspiel. Was ich nicht verstehe, sind Leute, die sich alle Mühe geben, um Cursor zu vermeiden, als hätten sie Cooties oder ähnliches, selbst wenn ein Cursor eine einfachere und / oder effizientere Lösung ist. Es ist der irrationale Hass, der mich verblüfft, nicht die offensichtlichen technischen Effizienzvorteile.

Steven A. Lowe
quelle
1
Ich denke, dass Ihre Bearbeitung alles sagt ... In fast allen Situationen (auf die ich gestoßen bin) gibt es eine Möglichkeit, einen Cursor durch eine leistungsbasierte satzbasierte Situation zu ersetzen. Sie sagen ein Kinderspiel, aber Sie verstehen den Unterschied.
StingyJack
7
Ich liebe die Tags zu dieser Frage!
Sep332
2
Der Teil über rekursive CTE-Grenzwerte 32ist Unsinn. Vermutlich denken Sie an rekursive Trigger und das Maximum @@NESTLEVELvon 32. Sie kann in der Abfrage OPTION (MAXRECURSION N)mit Standard 100und 0unbegrenzter Bedeutung festgelegt werden.
Martin Smith
@ MartinSmith: Das Standardlimit ist jetzt 100 und das Maximum
Steven A. Lowe
Nein, es ist immer noch genau das gleiche wie bei meinem Kommentar und in allen Versionen von SQL Server, die rekursive CTEs unterstützen. Wie Ihr Link sagt "Wenn 0 angegeben ist, wird kein Limit angewendet."
Martin Smith

Antworten:

74

Der "Overhead" mit Cursorn ist lediglich Teil der API. Cursor sind die Funktionsweise von Teilen des RDBMS unter der Haube. Oft CREATE TABLEund INSERThaben SELECTAnweisungen, und die Implementierung ist die offensichtliche interne Cursor-Implementierung.

Durch die Verwendung übergeordneter "satzbasierter Operatoren" werden die Cursorergebnisse zu einer einzigen Ergebnismenge gebündelt, was weniger API-Hin- und Her bedeutet.

Cursor sind älter als moderne Sprachen, die erstklassige Sammlungen bieten. Das alte C, COBOL, Fortran usw. mussten Zeilen einzeln verarbeiten, da es keinen Begriff für "Sammlung" gab, der weit verbreitet werden konnte. Java, C #, Python usw. verfügen über erstklassige Listenstrukturen, die Ergebnismengen enthalten.

Das langsame Problem

In einigen Kreisen sind die relationalen Verknüpfungen ein Rätsel, und die Leute schreiben verschachtelte Cursor anstatt einer einfachen Verknüpfung. Ich habe wirklich epische Nested-Loop-Operationen gesehen, die als viele, viele Cursor geschrieben wurden. RDBMS-Optimierung aufheben. Und sehr langsam laufen.

Einfache SQL-Umschreibungen, um verschachtelte Cursorschleifen durch Verknüpfungen zu ersetzen, und eine einzelne flache Cursorschleife können dazu führen, dass Programme zum 100. Mal ausgeführt werden. [Sie dachten, ich sei der Gott der Optimierung. Ich habe nur verschachtelte Schleifen durch Joins ersetzt. Noch verwendete Cursor.]

Diese Verwirrung führt oft zu einer Anklage gegen Cursor. Es ist jedoch nicht der Cursor, sondern der Missbrauch des Cursors, der das Problem darstellt.

Das Größenproblem

Für wirklich epische Ergebnismengen (dh das Speichern einer Tabelle in eine Datei) sind Cursor unerlässlich. Die satzbasierten Operationen können keine wirklich großen Ergebnismengen als einzelne Sammlung im Speicher materialisieren.

Alternativen

Ich versuche so viel wie möglich eine ORM-Ebene zu verwenden. Das hat aber zwei Zwecke. Zunächst werden die Cursor von der ORM-Komponente verwaltet. Zweitens wird die SQL von der Anwendung in eine Konfigurationsdatei getrennt. Es ist nicht so, dass die Cursor schlecht sind. Es ist so, dass das Codieren all dieser Öffnen, Schließen und Abrufen keine wertschöpfende Programmierung ist.

S.Lott
quelle
3
"Cursor sind die Funktionsweise des RDBMS unter der Haube." Wenn Sie speziell SQL Server meinen, OK, gut, das weiß ich nicht. Aber ich habe an den Interna mehrerer RDBMS (und ORDBMS) (unter Stonebraker) gearbeitet, und keiner von ihnen hat das getan. Beispiel: Ingres verwendet intern "Ergebnismengen" von Tupeln.
Richard T
@ Richard T: Ich arbeite an Informationen aus zweiter Hand über die RDBMS-Quelle. Ich werde die Erklärung ändern.
S.Lott
2
"Ich habe wirklich epische Nested-Loop-Operationen gesehen, die als viele, viele Cursor geschrieben wurden." Ich sehe sie auch immer wieder. Es ist schwer zu glauben.
RussellH
41

Cursor führen dazu, dass Menschen eine prozedurale Denkweise übermäßig auf eine satzbasierte Umgebung anwenden.

Und sie sind langsam !!!

Von SQLTeam :

Beachten Sie, dass Cursor die langsamste Möglichkeit sind, auf Daten in SQL Server zuzugreifen. Das sollte nur verwendet werden, wenn Sie wirklich jeweils auf eine Zeile zugreifen müssen. Der einzige Grund, den ich mir vorstellen kann, ist das Aufrufen einer gespeicherten Prozedur in jeder Zeile. Im Artikel Cursor Performance habe ich festgestellt, dass Cursor mehr als dreißigmal langsamer sind als satzbasierte Alternativen .

Galwegisch
quelle
6
Dieser Artikel ist 7 Jahre alt. Glauben Sie, dass sich die Dinge in der Zwischenzeit möglicherweise geändert haben?
Steven A. Lowe
1
Ich denke auch, dass Cursor sehr langsam sind und generell vermieden werden sollten. Wenn sich das OP jedoch auf die Frage bezog, von der ich glaube, dass sie es war, war ein Cursor dort die richtige Lösung (Streaming von Datensätzen nacheinander aufgrund von Speicherbeschränkungen).
Rmeador
Der aktualisierte Artikel korrigiert die relativen Geschwindigkeitsmessungen nicht, bietet jedoch einige gute Optimierungen und Alternativen. Beachten Sie, dass der ursprüngliche Artikel besagt, dass Cursor 50-mal schneller sind als while-Schleifen, was interessant ist
Steven A. Lowe
6
@ BoltBait: Ich persönlich denke, wenn Sie pauschale Aussagen wie diese machen, können Sie nicht wirklich 45 Jahre alt sein :-P
Steven A. Lowe
4
@ BoltBait: Ihr Kinder kommt von meinem Rasen!
Steven A. Lowe
19

Es gibt eine Antwort oben, die besagt: "Cursor sind die langsamste Möglichkeit, auf Daten in SQL Server zuzugreifen ... Cursor sind mehr als dreißigmal langsamer als satzbasierte Alternativen."

Diese Aussage mag unter vielen Umständen zutreffen, ist aber als pauschale Aussage problematisch. Zum Beispiel habe ich Cursor in Situationen gut eingesetzt, in denen ich einen Aktualisierungs- oder Löschvorgang ausführen möchte, der viele Zeilen einer großen Tabelle betrifft, die konstante Produktionslesevorgänge empfängt. Das Ausführen einer gespeicherten Prozedur, die diese Aktualisierungen zeilenweise ausführt, ist schneller als satzbasierte Operationen, da die satzbasierte Operation mit der Leseoperation in Konflikt steht und schreckliche Sperrprobleme verursacht (und das Produktionssystem möglicherweise vollständig zerstört). in Extremfällen).

In Ermangelung anderer Datenbankaktivitäten sind satzbasierte Vorgänge universell schneller. In Produktionssystemen kommt es darauf an.

davidcl
quelle
1
Klingt nach der Ausnahme, die die Regel bestätigt.
Joel Coehoorn
6
@ [Joel Coehoorn]: Ich habe dieses Sprichwort nie verstanden.
Steven A. Lowe
2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html verstehen Ausnahme als "was ausgelassen wird" und beachten Sie, dass die Regel hier so etwas wie "in den meisten Situationen Cursor sind Schlecht".
David Lay
1
@delm: danke für den link, jetzt verstehe ich den satz noch weniger!
Steven A. Lowe
5
@ [Steven A. Lowe] Grundsätzlich heißt es, wenn Sie eine Regel mit einem Unterfall "brechen", muss es eine allgemeine Regel geben, um zu brechen, also existiert eine Regel. zB von Link: ("Wenn wir eine Erklärung wie" Eintritt ist sonntags kostenlos "haben, können wir davon ausgehen, dass der Eintritt in der Regel kostenpflichtig ist.")
Fry
9

Cursor werden normalerweise von SQL-Entwicklern an Orten verwendet, an denen satzbasierte Operationen besser wären. Insbesondere wenn Benutzer SQL nach dem Erlernen einer traditionellen Programmiersprache lernen, führt die Mentalität "Über diese Datensätze iterieren" dazu, dass Benutzer Cursor unangemessen verwenden.

Die meisten seriösen SQL-Bücher enthalten ein Kapitel, in dem die Verwendung von Cursorn beschrieben wird. Gut geschriebene machen deutlich, dass Cursor ihren Platz haben, aber nicht für satzbasierte Operationen verwendet werden sollten.

Es gibt offensichtlich Situationen, in denen Cursor die richtige Wahl sind oder zumindest eine richtige Wahl.

davidcl
quelle
9

Der Optimierer kann die relationale Algebra häufig nicht verwenden, um das Problem zu transformieren, wenn eine Cursormethode verwendet wird. Oft ist ein Cursor eine gute Möglichkeit, ein Problem zu lösen, aber SQL ist eine deklarative Sprache, und die Datenbank enthält viele Informationen, von Einschränkungen bis hin zu Statistiken und Indizes, sodass der Optimierer viele Optionen zur Lösung des Problems hat Problem, während ein Cursor die Lösung ziemlich explizit steuert.

Cade Roux
quelle
8

In Oracle führen PL / SQL-Cursor nicht zu Tabellensperren, und es ist möglich, das Sammeln und Abrufen von Massen zu verwenden.

In Oracle 10 der häufig verwendete implizite Cursor

  for x in (select ....) loop
    --do something 
  end loop;

ruft implizit 100 Zeilen gleichzeitig ab. Explizites Sammeln / Abrufen von Massen ist ebenfalls möglich.

PL / SQL-Cursor sind jedoch ein letzter Ausweg. Verwenden Sie sie, wenn Sie ein Problem mit satzbasiertem SQL nicht lösen können.

Ein weiterer Grund ist die Parallelisierung. Für die Datenbank ist es einfacher, große satzbasierte Anweisungen zu parallelisieren als zeilenweisen Imperativcode. Aus demselben Grund wird die funktionale Programmierung immer beliebter (Haskell, F #, Lisp, C # LINQ, MapReduce ...). Die funktionale Programmierung erleichtert die Parallelisierung. Die Anzahl der CPUs pro Computer steigt, sodass die Parallelisierung immer mehr zum Problem wird.

tuinstoel
quelle
6

Im Allgemeinen ist die Leistung von Code mit Cursorn in einer relationalen Datenbank um eine Größenordnung schlechter als bei satzbasierten Operationen.

Charles Bretana
quelle
Haben Sie einen Benchmark oder eine Referenz dafür? Ich habe keinen derart drastischen Leistungsabfall bemerkt ... aber vielleicht haben meine Tabellen nicht genug Zeilen, um eine Rolle zu spielen (normalerweise eine Million oder weniger)?
Steven A. Lowe
Oh, warte, ich verstehe, was du meinst - aber ich würde niemals befürworten, Cursor anstelle von festgelegten Operationen zu verwenden, nur nicht bis zum Äußersten zu gehen, um Cursor zu vermeiden
Steven A. Lowe,
3
Ich erinnere mich an das erste Mal, als ich SQL ausführte. Wir mussten eine tägliche 50.000-Datendatei von einem Mainframe in eine SQL Server-Datenbank importieren. Ich verwendete einen Cursor und stellte fest, dass der Import mit dem Cursor ungefähr 26 Stunden dauerte. Als ich zu satzbasierten Vorgängen wechselte, dauerte der Vorgang 20 Minuten.
Charles Bretana
6

Die obigen Antworten haben die Wichtigkeit des Schließens nicht genug betont. Ich bin kein großer Fan von Cursorn, weil sie oft zu Sperren auf Tabellenebene führen.

Richard T.
quelle
1
ja Dankeschön! Ohne Optionen, um dies zu verhindern (schreibgeschützt, nur weiterleiten usw.), werden sie dies sicherlich tun, ebenso wie jede (SQL Server-) Operation, die mehrere Zeilen und dann mehrere Seiten mit Zeilen belegt.
Steven A. Lowe
?? Das ist ein Problem mit Ihrer Sperrstrategie, NICHT mit Cursorn. Sogar eine SELECT-Anweisung fügt Lesesperren hinzu.
Adam
3

Für das, was es wert ist, habe ich gelesen, dass die "eine" Stelle, an der ein Cursor sein satzbasiertes Gegenstück ausführt, eine laufende Summe ist. Bei einer kleinen Tabelle begünstigt die Geschwindigkeit, mit der die Zeilen über die Reihenfolge nach Spalten summiert werden, die satzbasierte Operation. Mit zunehmender Zeilengröße der Tabelle wird der Cursor jedoch schneller, da der laufende Gesamtwert einfach zum nächsten Durchgang der Tabelle übertragen werden kann Schleife. Nun, wo Sie eine laufende Summe machen sollten, ist ein anderes Argument ...

Eric Sabine
quelle
1
Wenn Sie unter "Running Total" eine Aggregation (min, max, sum) verstehen, schlägt jedes kompetente DBMS einer clientseitigen, cursorbasierten Lösung die Hosen aus, schon allein deshalb, weil die Funktion in der Engine und ausgeführt wird Es gibt keinen Client-Server-Overhead. Vielleicht ist SQL Server nicht kompetent?
Richard T
1
@ [Richard T]: Wir diskutieren serverseitige Cursor wie in einer gespeicherten Prozedur, nicht clientseitige Cursor. Entschuldigung für die Verwirrung!
Steven A. Lowe
2

Abgesehen von den (Nicht-) Leistungsproblemen denke ich, dass das größte Versagen von Cursorn darin besteht, dass das Debuggen schmerzhaft ist. Insbesondere im Vergleich zu Code in den meisten Clientanwendungen, in denen das Debuggen vergleichsweise einfach und die Sprachfunktionen in der Regel viel einfacher sind. Tatsächlich behaupte ich, dass fast alles, was man in SQL mit einem Cursor macht, wahrscheinlich überhaupt erst in der Client-App passieren sollte.

Wyatt Barnett
quelle
2
Das Debuggen von SQL ist auch ohne Cursor schmerzhaft. MS SQL-Step-Through-Tools in Visual Studio scheinen mich nicht zu mögen (sie hängen viel oder lösen überhaupt keine Haltepunkte aus), daher bin ich normalerweise auf PRINT-Anweisungen reduziert ;-)
Steven A. Lowe
1

Können Sie dieses Cursor-Beispiel posten oder auf die Frage verlinken? Es gibt wahrscheinlich einen noch besseren Weg als einen rekursiven CTE.

Zusätzlich zu anderen Kommentaren verursachen Cursor bei unsachgemäßer Verwendung (was häufig der Fall ist) unnötige Seiten- / Zeilensperren.

Gordon Bell
quelle
1
Es gibt einen besseren Weg - einen verdammten Cursor ;-)
Steven A. Lowe
1

Sie hätten Ihre Frage wahrscheinlich nach dem zweiten Absatz abschließen können, anstatt die Leute "verrückt" zu nennen, nur weil sie einen anderen Standpunkt vertreten als Sie, und auf andere Weise zu versuchen, Fachleute zu verspotten, die möglicherweise einen sehr guten Grund haben, sich so zu fühlen, wie sie es tun.

In Bezug auf Ihre Frage gibt es zwar sicherlich Situationen, in denen ein Cursor erforderlich sein kann, aber meiner Erfahrung nach entscheiden Entwickler, dass ein Cursor weitaus häufiger "verwendet" werden muss, als dies tatsächlich der Fall ist. Die Wahrscheinlichkeit, dass jemand auf der Seite von zu viel Verwendung von Cursorn irrt, anstatt sie nicht zu verwenden, wenn er sollte, ist meiner Meinung nach VIEL höher.

Tom H.
quelle
8
Bitte lesen Sie genauer, Tom - der genaue Ausdruck war "wahnsinniger Hass"; "gehasst" war das Objekt des Adjektivs "verrückt", nicht "Menschen". Englisch kann manchmal etwas schwierig sein ;-)
Steven A. Lowe
0

Grundsätzlich 2 Codeblöcke, die dasselbe tun. Vielleicht ist es ein etwas seltsames Beispiel, aber es beweist den Punkt. SQL Server 2005:

SELECT * INTO #temp FROM master..spt_values
DECLARE @startTime DATETIME

BEGIN TRAN 

SELECT @startTime = GETDATE()
UPDATE #temp
SET number = 0
select DATEDIFF(ms, @startTime, GETDATE())

ROLLBACK 

BEGIN TRAN 
DECLARE @name VARCHAR

DECLARE tempCursor CURSOR
    FOR SELECT name FROM #temp

OPEN tempCursor

FETCH NEXT FROM tempCursor 
INTO @name

SELECT @startTime = GETDATE()
WHILE @@FETCH_STATUS = 0
BEGIN

    UPDATE #temp SET number = 0 WHERE NAME = @name
    FETCH NEXT FROM tempCursor 
    INTO @name

END 
select DATEDIFF(ms, @startTime, GETDATE())
CLOSE tempCursor
DEALLOCATE tempCursor

ROLLBACK 
DROP TABLE #temp

Das einzelne Update dauert 156 ms, während der Cursor 2016 ms benötigt.

Mladen Prajdic
quelle
3
Nun ja, es beweist, dass dies eine wirklich dumme Art ist, einen Cursor zu benutzen! Was aber, wenn die Aktualisierung jeder Zeile vom Wert der vorherigen Zeile in der Datumsreihenfolge abhängt?
Steven A. Lowe
BEGIN TRAN SELECT TOP 1 Basiswert FROM Tabelle ORDER BY Zeitstempel DESC INSERT Tabelle (Felder) VALUES (Werte, einschließlich des abgeleiteten Werts aus dem vorherigen Datensatz) COMMIT TRAN
dkretz
@doofledorfer: Das würde eine Zeile basierend auf der letzten Zeile nach Datum einfügen und nicht jede Zeile um einen Wert aus der vorherigen Zeile in Datumsreihenfolge aktualisieren
Steven A. Lowe
Um den Cursor wirklich zu benutzen, sollten Sie WHERE CURRENT OF im Update verwenden
erikkallen