Perché le persone odiano così tanto i cursori SQL? [chiuso]


127

Capisco il voler evitare di dover usare un cursore a causa del sovraccarico e dell'inconveniente, ma sembra che ci sia un certo cursore-fobia-mania in atto in cui le persone stanno facendo di tutto per evitare di doverne usare uno.

Ad esempio, una domanda chiedeva come fare qualcosa di ovviamente banale con un cursore e la risposta accettata proposta usando una query ricorsiva con espressione di tabella comune (CTE) con una funzione personalizzata ricorsiva, anche se questo limita il numero di righe che potrebbero essere elaborate a 32 (a causa del limite di chiamata di funzione ricorsiva nel server sql). Questo mi sembra una terribile soluzione per la longevità del sistema, per non parlare di uno sforzo tremendo solo per evitare di usare un semplice cursore.

Qual è la ragione di questo livello di odio folle? Qualche "autorità nota" ha emesso una fatwa contro i cursori? Qualche male indicibile si nasconde nel cuore dei cursori che corrompe la morale dei bambini o qualcosa del genere?

Domanda Wiki, più interessata alla risposta che al rappresentante.

Informazioni correlate:

Cursori di avanzamento rapido di SQL Server

EDIT: vorrei essere più preciso: capisco che i cursori non dovrebbero essere usati al posto delle normali operazioni relazionali ; questo è un gioco da ragazzi. Quello che non capisco è che le persone si sforzano di evitare i cursori come se avessero cootie o qualcosa del genere, anche quando un cursore è una soluzione più semplice e / o più efficiente. È l'odio irrazionale che mi confonde, non le evidenti efficienze tecniche.


1
Penso che la tua Modifica dica tutto ... In quasi tutte le situazioni (che ho incontrato) c'è un modo per sostituire un cursore con una situazione basata su set con prestazioni migliori. Dici un gioco da ragazzi, ma capisci la differenza.
StingyJack,

7
Adoro i tag su questa domanda!
sep332,

2
La parte relativa ai limiti CTE ricorsivi 32è senza senso. Presumibilmente stai pensando a trigger ricorsivi e al massimo @@NESTLEVELdi 32. Può essere impostato nella query OPTION (MAXRECURSION N)con impostazione predefinita 100e 0significato illimitato.
Martin Smith,

@MartinSmith: il limite predefinito ora è 100 e il massimo è 32K sql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe,

No, è sempre esattamente lo stesso di quando ho fatto il mio commento e in tutte le versioni di SQL Server che supportano CTE ricorsive. Come dice il tuo link "Quando si specifica 0, non viene applicato alcun limite."
Martin Smith,

Risposte:


74

Il "sovraccarico" con i cursori fa semplicemente parte dell'API. I cursori sono il modo in cui parti dell'RDBMS funzionano sotto il cofano. Spesso CREATE TABLEe INSERThannoSELECT dichiarazioni, e l'implementazione è l'evidente implementazione del cursore interno.

L'uso di "operatori basati su set" di livello superiore raggruppa i risultati del cursore in un singolo set di risultati, il che significa meno API avanti e indietro.

I cursori precedono le lingue moderne che offrono collezioni di prima classe. I vecchi C, COBOL, Fortran, ecc., Dovevano elaborare le righe una alla volta perché non esisteva la nozione di "raccolta" che potesse essere ampiamente utilizzata. Java, C #, Python, ecc., Hanno strutture di elenco di prima classe per contenere set di risultati.

Il problema lento

In alcuni ambienti, i join relazionali sono un mistero e la gente scriverà cursori nidificati anziché un semplice join. Ho visto operazioni di loop nidificati veramente epiche scritte come molti e molti cursori. Sconfiggere un'ottimizzazione RDBMS. E correndo molto lentamente.

Le riscritture SQL semplici per sostituire i loop di cursore nidificati con join e un singolo loop di cursore piatto possono eseguire i programmi al 100 ° tempo. [Pensavano che fossi il dio dell'ottimizzazione. Tutto quello che ho fatto è stato sostituire i loop nidificati con join. Cursori ancora usati.]

Questa confusione porta spesso a un'accusa di cursori. Tuttavia, non è il cursore, è l'abuso del cursore che è il problema.

Il problema delle dimensioni

Per insiemi di risultati davvero epici (ovvero, scaricare una tabella in un file), i cursori sono essenziali. Le operazioni basate su set non possono materializzare set di risultati molto grandi come un'unica raccolta in memoria.

alternative

Cerco di utilizzare un livello ORM il più possibile. Ma questo ha due scopi. Innanzitutto, i cursori sono gestiti dal componente ORM. In secondo luogo, l'SQL è separato dall'applicazione in un file di configurazione. Non è che i cursori siano cattivi. È che codificare tutte quelle aperture, chiusure e recuperi non è una programmazione a valore aggiunto.


3
"I cursori sono come l'RDBMS funziona sotto il cofano." Se intendi specificamente SQL Server, OK, va bene, lo ignoro. Ma ho lavorato sugli interni di più RDBMS (e ORDBMS) (sotto Stonebraker) e nessuno di loro lo ha fatto. Ad esempio: Ingres utilizza ciò che equivale a "set di risultati" di tuple internamente.
Richard T,

@Richard T: sto lavorando su informazioni di seconda mano sulla fonte RDBMS; Modificherò la dichiarazione.
S.Lott

2
"Ho visto operazioni di loop nidificati veramente epiche scritte come un sacco di cursori." Continuo a vederli anche io. È difficile da credere.
RussellH,

41

I cursori fanno sì che le persone applichino eccessivamente una mentalità procedurale a un ambiente basato su set.

E sono LENTI !!!

Da SQLTeam :

Si noti che i cursori sono il modo più LENTO per accedere ai dati all'interno di SQL Server. Dovrebbe essere usato solo quando è veramente necessario accedere a una riga alla volta. L'unica ragione a cui riesco a pensare è quella di chiamare una procedura memorizzata su ogni riga. Nell'articolo Cursor Performance ho scoperto che i cursori sono oltre trenta volte più lenti delle alternative basate su set .


6
quell'articolo ha 7 anni, pensi che forse le cose potrebbero essere cambiate nel frattempo?
Steven A. Lowe,

1
Penso anche che i cursori siano molto lenti e da evitare, in generale. Tuttavia, se l'OP si riferiva alla domanda che penso fosse, allora un cursore era la soluzione corretta lì (lo streaming registra uno alla volta a causa di vincoli di memoria).
rmeador,

l'articolo aggiornato non corregge le misurazioni della velocità relativa, ma fornisce alcune buone ottimizzazioni e alternative. Si noti che l'articolo originale afferma che i cursori sono 50 volte più veloci di mentre i loop, il che è interessante
Steven A. Lowe,

6
@BoltBait: personalmente penso che se fai affermazioni generali come quelle non puoi davvero avere 45 anni :-P
Steven A. Lowe,

4
@BoltBait: voi ragazzi scendete dal mio prato!
Steven A. Lowe,

19

C'è una risposta sopra che dice "i cursori sono il modo più LENTO per accedere ai dati all'interno di SQL Server ... i cursori sono oltre trenta volte più lenti delle alternative basate sul set".

Questa affermazione può essere vera in molte circostanze, ma come affermazione generale è problematica. Ad esempio, ho fatto buon uso dei cursori in situazioni in cui desidero eseguire un aggiornamento o eliminare un'operazione che interessa molte righe di una tabella di grandi dimensioni che riceve letture di produzione costanti. L'esecuzione di una procedura memorizzata che esegue questi aggiornamenti una riga alla volta risulta essere più veloce delle operazioni basate su set, poiché l'operazione basata su set è in conflitto con l'operazione di lettura e finisce per causare orribili problemi di blocco (e potrebbe uccidere del tutto il sistema di produzione, in casi estremi).

In assenza di altre attività del database, le operazioni basate su set sono universalmente più veloci. Nei sistemi di produzione, dipende.


1
Sembra l'eccezione che dimostra la regola.
Joel Coehoorn,

6
@ [Joel Coehoorn]: non ho mai capito questo detto.
Steven A. Lowe,

2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html capiscono l'eccezione come "ciò che è lasciato fuori" e nota che la regola qui è qualcosa di simile "nella maggior parte dei cursori di situazione sono male".
David Lay

1
@delm: grazie per il link, ora capisco ancora meno la frase!
Steven A. Lowe,

5
@ [Steven A. Lowe] In pratica sta dicendo che se "infrangi una regola" con una sottocassa, deve esserci una regola generale da infrangere, allora esiste una regola. ad es. da Link: ("Se abbiamo una dichiarazione come" l'ingresso è gratuito la domenica ", possiamo ragionevolmente presumere che, come regola generale, l'ingresso sia addebitato.")
Frig

9

I cursori tendono ad essere utilizzati iniziando gli sviluppatori SQL in luoghi in cui le operazioni basate su set sarebbero migliori. Soprattutto quando le persone imparano l'SQL dopo aver appreso un linguaggio di programmazione tradizionale, la mentalità "iterare su questi record" tende a indurre le persone a usare i cursori in modo inappropriato.

I libri SQL più seri includono un capitolo che impone l'uso dei cursori; quelli ben scritti chiariscono che i cursori hanno il loro posto ma non dovrebbero essere usati per operazioni basate su set.

Ci sono ovviamente situazioni in cui i cursori sono la scelta corretta, o almeno una scelta corretta.


9

L'ottimizzatore spesso non può utilizzare l'algebra relazionale per trasformare il problema quando si utilizza un metodo cursore. Spesso un cursore è un ottimo modo per risolvere un problema, ma SQL è un linguaggio dichiarativo e ci sono molte informazioni nel database, dai vincoli, alle statistiche e agli indici, il che significa che l'ottimizzatore ha molte opzioni per risolvere il problema, mentre un cursore dirige in modo esplicito la soluzione.


8

In Oracle i cursori PL / SQL non comporteranno blocchi della tabella ed è possibile utilizzare la raccolta di massa / recupero di massa.

In Oracle 10 il cursore implicito spesso usato

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

recupera implicitamente 100 righe alla volta. È anche possibile la raccolta / recupero in blocco espliciti.

Tuttavia i cursori PL / SQL sono l'ultima risorsa, usali quando non sei in grado di risolvere un problema con SQL basato su set.

Un altro motivo è la parallelizzazione, è più facile per il database parallelizzare grandi istruzioni basate su set rispetto al codice imperativo riga per riga. È lo stesso motivo per cui la programmazione funzionale diventa sempre più popolare (Haskell, F #, Lisp, C # LINQ, MapReduce ...), la programmazione funzionale semplifica la parallelizzazione. Il numero di CPU per computer sta aumentando, quindi la parallelizzazione diventa sempre più un problema.


6

In generale, poiché su un database relazionale, le prestazioni del codice usando i cursori sono un ordine di grandezza peggiore delle operazioni basate su set.


hai un benchmark o un riferimento per questo? non ho notato alcun degrado delle prestazioni così drastico ... ma forse i miei tavoli non hanno abbastanza righe per importare (un milione o meno, di solito)?
Steven A. Lowe,

oh aspetta, capisco cosa intendi, ma non vorrei mai sostenere l'uso dei cursori invece delle operazioni impostate, solo non andare agli estremi per evitare i cursori
Steven A. Lowe,

3
Ricordo la prima volta che ho fatto SQL, abbiamo dovuto importare un file di dati giornaliero di 50k da un mainframe in un database SQL Server ... Ho usato un cursore e ho scoperto che l'importazione impiegava circa 26 ore usando il cursore .. Quando sono passato alle operazioni basate su set, il processo ha richiesto 20 minuti.
Charles Bretana,

6

Le risposte sopra non hanno sottolineato abbastanza l'importanza del blocco. Non sono un grande fan dei cursori perché spesso provocano blocchi a livello di tabella.


1
si Grazie! Senza opzioni per impedirlo (sola lettura, solo forward, ecc.) Lo faranno sicuramente, così come qualsiasi operazione (server sql) che procede ad occupare più righe e quindi diverse pagine di righe.
Steven A. Lowe,

?? Questo è un problema con la tua strategia di blocco NON con i cursori. Anche un'istruzione SELECT aggiungerà blocchi di lettura.
Adam

3

Per quello che vale, ho letto che il "solo" posto in cui un cursore eseguirà la sua controparte basata su set è in un totale parziale. Su una piccola tabella la velocità di sommare le righe sull'ordine per colonne favorisce l'operazione basata su set ma quando la tabella aumenta di dimensioni della riga il cursore diventerà più veloce perché può semplicemente trasportare il valore totale corrente al passaggio successivo del ciclo continuo. Ora dove dovresti fare un totale parziale è un argomento diverso ...


1
Se si intende "totale parziale" un'aggregazione di qualche tipo (min, max, somma), qualsiasi DBMS competente batterà i pantaloni di una soluzione basata sul cursore sul lato client, se non altro perché la funzione viene eseguita nel motore e non è presente alcun overhead del server <--> client. Forse SQL Server non è competente?
Richard T

1
@ [Richard T]: stiamo discutendo dei cursori sul lato server, come all'interno di una procedura memorizzata, non sui cursori sul lato client; dispiace per la confusione!
Steven A. Lowe,


2

Al di fuori dei problemi di performance (non), penso che il più grande fallimento dei cursori sia che sono dolorosi da eseguire il debug. Soprattutto rispetto al codice nella maggior parte delle applicazioni client in cui il debug tende ad essere relativamente semplice e le funzionalità del linguaggio tendono ad essere molto più facili. In realtà, sostengo che quasi tutto ciò che si sta facendo in SQL con un cursore dovrebbe probabilmente accadere in primo luogo nell'app client.


2
SQL è doloroso per il debug, anche senza cursori. Gli strumenti step-through di MS SQL in Visual Studio non mi piacciono (si bloccano molto o non inciampano affatto nei punti di interruzione), quindi di solito sono ridotto alle istruzioni PRINT ;-)
Steven A. Lowe

1

Puoi pubblicare quell'esempio di cursore o un link alla domanda? C'è probabilmente un modo persino migliore di un CTE ricorsivo.

Oltre ad altri commenti, i cursori se usati in modo improprio (che spesso è) causano blocchi pagina / riga non necessari.


1
c'è un modo migliore - un cursore strano ;-)
Steven A. Lowe,

1

Probabilmente avresti potuto concludere la tua domanda dopo il secondo paragrafo, piuttosto che chiamare le persone "pazze" semplicemente perché hanno un punto di vista diverso da quello che fai tu e tentare altrimenti di deridere i professionisti che potrebbero avere un'ottima ragione per sentirsi come loro.

Per quanto riguarda la tua domanda, mentre ci sono certamente situazioni in cui un cursore può essere richiesto, nella mia esperienza gli sviluppatori decidono che un cursore "deve" essere usato molto più spesso di quanto non sia effettivamente il caso. A mio avviso, la possibilità che qualcuno commetta errori sul lato dell'uso eccessivo dei cursori rispetto al non usarli quando dovrebbero dovrebbe essere MOLTO più alta.


8
per favore leggi più attentamente, Tom - la frase esatta era "odio folle"; "odiato" era l'oggetto dell'aggettivo "folle", non "persone". L'inglese a volte può essere un po 'difficile ;-)
Steven A. Lowe,

0

praticamente 2 blocchi di codice che fanno la stessa cosa. forse è un esempio un po 'strano ma dimostra il punto. 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

il singolo aggiornamento richiede 156 ms mentre il cursore impiega 2016 ms.


3
bene sì, dimostra che questo è un modo davvero stupido di usare un cursore! ma cosa succederebbe se l'aggiornamento di ogni riga dipendesse dal valore della riga precedente nell'ordine della data?
Steven A. Lowe

INIZIA TRAN SELEZIONA TOP 1 basale DA tabella ORDINA per data e ora DESC INSERISCI tabella (campi) VALORI (valori, incluso il valore derivato dal record precedente) COMMIT TRAN
dkretz

@doofledorfer: che inserisce una riga in base all'ultima riga per data, non aggiorna ogni riga di un valore dalla riga precedente nell'ordine di data
Steven A. Lowe

Per usare veramente il cursore dovresti usare DOVE CORRENTE DI nell'aggiornamento
erikkallen,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.