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

Riesco a capire di voler evitare di dover usare un cursore a causa del sovraccarico e dei disagi, ma sembra che ci sia una grave fobia del mania del cursore in corso in cui le persone fanno di tutto per evitare di doverne usare uno.

Ad esempio, una domanda ha chiesto come fare qualcosa ovviamente ovvio con un cursore e la risposta accettata proposta utilizzando una query ricorsiva di espressione di tabella comune (CTE) con una funzione personalizzata ricorsiva, anche se ciò limita il numero di righe che potrebbero essere elaborate a 32 (a causa del limite di chiamata della funzione ricorsiva in SQL Server). Questo mi sembra una terribile soluzione per la longevità del sistema, per non parlare di uno sforzo tremendo per evitare l’uso di un semplice cursore.

Qual è la ragione di questo livello di odio folle? Qualche “autorevole autorità” ha emesso una fatwa contro i cursori? Qualche male indicibile si annida nel cuore dei cursori che corrompono la morale dei bambini o qualcosa del genere?

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

Informazioni correlate:

Cursori di inoltro veloce 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 allontanano per evitare cursori come se fossero cooties o qualcosa del genere, anche quando un cursore è una soluzione più semplice e / o più efficiente. È l’odio irrazionale che mi sconcerta, non le evidenti efficienze tecniche.

Il “sovraccarico” con i cursori è solo una parte dell’API. I cursori sono come le parti del RDBMS funzionano sotto il cofano. Spesso CREATE TABLE e INSERT hanno istruzioni SELECT e l’implementazione è l’ovvia implementazione del cursore interno.

L’utilizzo di “operatori basati su set” di livello superiore raggruppa i risultati del cursore in un unico set di risultati, ovvero meno avanti e indietro dell’API.

I cursori precedono le lingue moderne che forniscono raccolte di prima class. Old C, COBOL, Fortran, ecc. Dovevano elaborare le righe una alla volta perché non esisteva la nozione di “collezione” che potesse essere ampiamente utilizzata. Java, C #, Python, ecc. Hanno strutture di lista di prima class per contenere i set di risultati.

Il problema lento

In alcuni ambienti, i join relazionali sono un mistero e la gente scriverà i cursori nidificati anziché un semplice join. Ho visto operazioni di loop annidate davvero epiche scritte come tanti e tanti cursori. Sconfiggere un’ottimizzazione RDBMS. E funziona molto lentamente.

SQL semplice riscrive per sostituire loop di cursori nidificati con join e un singolo ciclo di cursore piatto può far funzionare i programmi in 100 ° volta. [Pensavano che fossi il dio dell’ottimizzazione. Tutto ciò che ho fatto è stato sostituire i cicli annidati con i join. Cursori usati ancora.]

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

Il problema delle dimensioni

Per i set di risultati davvero epici (ad esempio, scaricare una tabella in un file), i cursori sono essenziali. Le operazioni basate su set non possono materializzare set di risultati veramente grandi come una singola raccolta in memoria.

alternative

Cerco di usare un layer ORM il più ansible. Ma questo ha due scopi. Innanzitutto, i cursori sono gestiti dal componente ORM. In secondo luogo, l’SQL viene separato dall’applicazione in un file di configurazione. Non è che i cursori siano cattivi. È che la codifica di tutti quelli che si aprono, si chiude e che recupera non è una programmazione a valore aggiunto.

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

E sono LENTILI !!!

Da SQLTeam :

Si noti che i cursori sono il modo più lento per accedere ai dati in SQL Server. Il dovrebbe essere usato solo quando hai veramente bisogno di accedere a una riga alla volta. L’unica ragione per cui posso pensare è chiamare una stored procedure su ogni riga. Nell’articolo Cursor Performance ho scoperto che i cursori sono oltre trenta volte più lenti delle alternative basate su set .

C’è una risposta sopra la quale dice che “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 su set”.

Questa affermazione può essere vera in molte circostanze, ma come affermazione generale è problematica. Ad esempio, ho fatto buon uso dei cursori nelle situazioni in cui voglio eseguire un’operazione di aggiornamento o eliminazione che interessa molte righe di una tabella di grandi dimensioni che riceve letture di produzione costanti. L’esecuzione di una stored procedure che fa questi aggiornamenti una riga alla volta finisce per essere più veloce delle operazioni basate su set, perché l’operazione basata su set è in conflitto con l’operazione di lettura e causa problemi di blocco orribili (e può uccidere completamente 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.

I cursori tendono ad essere utilizzati avviando gli sviluppatori SQL in luoghi in cui le operazioni basate su set sarebbero migliori. Soprattutto quando le persone imparano SQL dopo aver imparato 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 prevede l’uso di 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.

L’ottimizzatore spesso non può utilizzare l’algebra relazionale per trasformare il problema quando viene utilizzato un metodo di puntatore. 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. problema, mentre un cursore praticamente dirige esplicitamente la soluzione.

Nei cursori Oracle PL / SQL non risultano blocchi della tabella ed è ansible utilizzare la raccolta collettiva / raccolta di massa.

In Oracle 10 il cursore implicito spesso utilizzato

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

recupera implicitamente 100 file alla volta. È anche ansible la raccolta di massa / raccolta di massa esplicita.

Tuttavia, i cursori PL / SQL sono una soluzione estrema, li usano quando non si è in grado di risolvere un problema con SQL set-based.

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 aumenta, quindi la parallelizzazione diventa sempre più un problema.

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

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

Per quello che vale, ho letto che il posto “uno” che un cursore eseguirà la sua controparte basata su set è in un totale parziale. Su una piccola tabella la velocità di riassumere le righe sopra l’ordine per colonne favorisce l’operazione basata su set, ma man mano che la tabella aumenta di dimensioni della riga, il cursore diventerà più veloce perché può semplicemente portare il valore totale corrente al prossimo passaggio del ciclo continuo. Ora dove dovresti fare un totale parziale è un argomento diverso …

Al di fuori delle prestazioni (non) problemi, penso che il più grande fallimento dei cursori è che sono dolorose per il debug. Soprattutto rispetto al codice nella maggior parte delle applicazioni client in cui il debug tende ad essere relativamente facile e le caratteristiche del linguaggio tendono ad essere molto più semplici. In realtà, sostengo che quasi tutto ciò che si sta facendo in SQL con un cursore dovrebbe probabilmente accadere nell’app client in primo luogo.

Puoi pubblicare l’esempio del cursore o il link alla domanda? C’è probabilmente un modo migliore di un CTE ricorsivo.

Oltre ad altri commenti, i cursori utilizzati in modo improprio (che spesso) causano blocchi di pagine / righe non necessari.

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 e altrimenti stanno cercando di deridere i professionisti che potrebbero avere una buona 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 FAR più spesso di quanto sia effettivamente il caso. La mia opinione è che la possibilità che qualcuno commetta un errore nell’usare troppi cursori o non usarli quando dovrebbero è molto più alta.

basicamente 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 impiega 156 ms mentre il cursore impiega 2016 ms.