Domanda

È generalmente accettato che l'uso dei cursori nelle procedure memorizzate dovrebbe essere evitato ove possibile (sostituiti con logica basata su set, ecc.).Se si prendono i casi in cui è necessario eseguire l'iterazione su alcuni dati e è possibile farlo in modalità di sola lettura, il cursore di avanzamento rapido (sola lettura in avanti) è più o meno inefficiente rispetto ai cicli while?Dalle mie indagini sembra che l'opzione del cursore sia generalmente più veloce e utilizzi meno letture e tempo della CPU.Non ho effettuato test approfonditi, ma è questo ciò che trovano gli altri?I cursori di questo tipo (avanzamento veloce) comportano un sovraccarico o una risorsa aggiuntiva che potrebbe essere costosa di cui non sono a conoscenza.

Tutto ciò che si parla di non usare i cursori è davvero quello di evitare l'uso dei cursori quando sono disponibili approcci basati su set e l'uso di cursori aggiornabili, ecc.

Grazie

È stato utile?

Soluzione

La "migliore pratica" per evitare i cursori in SQL Server risale a SQL Server 2000 e versioni precedenti.La riscrittura del motore in SQL 2005 ha risolto la maggior parte dei problemi legati ai cursori, in particolare con l'introduzione dell'opzione di avanzamento rapido.I cursori non sono necessariamente peggiori di quelli basati su set e vengono utilizzati ampiamente e con successo in Oracle PL/SQL (LOOP).

Il "generalmente accettato" a cui ti riferisci era valido, ma ora è obsoleto e errato: parti dal presupposto che i cursori di avanzamento rapido si comportino come pubblicizzato e funzionino.Esegui alcuni test e ricerche, basando i tuoi risultati su SQL2005 e versioni successive

Altri suggerimenti

Sebbene un cursore di avanzamento rapido abbia alcune ottimizzazioni in Sql Server 2005, lo è non è vero che sono vicini a una query basata su set in termini di prestazioni.Esistono pochissime situazioni in cui la logica del cursore non può essere sostituita da una query basata su set.I cursori saranno sempre intrinsecamente più lenti, in parte a causa del fatto che devi continuare a interrompere l'esecuzione per riempire le variabili locali.

Ecco alcuni riferimenti, che sarebbero solo la punta dell'iceberg se si ricerca questo problema:

http://www.code-magazine.com/Article.aspx?quickid=060113

http://dataeducation.com/re-inventing-the-recursive-cte/

Questa risposta spera di consolidare le risposte fornite fino ad oggi.

1) Se possibile, utilizza la logica basata su set per le tue query, ad es.prova a usare solo SELECT, INSERT, UPDATE O DELETE con l'apposito FROM clausole o query annidate: saranno quasi sempre più veloci.

2) Se quanto sopra non è possibile, in SQL Server 2005+ FAST FORWARD i cursori sono efficienti e funzionano bene e dovrebbero essere usati preferibilmente ai cicli while.

"Se desideri un cursore ancora più veloce di AVANTI VELOCE, utilizza un cursore STATICO.Sono più veloci di FAST FORWARD.Non estremamente più veloce ma può fare la differenza."

Non così in fretta!Secondo Microsoft:"In genere, quando si verificavano queste conversioni, il tipo di cursore veniva degradato a un tipo di cursore "più costoso".In genere, un cursore (FAST) FORWARD-ONLY è il più performante, seguito da DYNAMIC, KEYSET e infine STATIC che è generalmente il meno performante."

da: http://blogs.msdn.com/b/mssqlisv/archive/2006/06/23/644493.aspx

Puoi evitare i cursori per la maggior parte del tempo, ma a volte è necessario.

Tieni presente che FAST_FORWARD è DINAMICO...FORWARD_ONLY è possibile utilizzare con un cursore STATICO.

Prova ad usarlo sul problema di Halloween per vedere cosa succede!!!

IF OBJECT_ID('Funcionarios') IS NOT NULL
DROP TABLE Funcionarios
GO

CREATE TABLE Funcionarios(ID          Int IDENTITY(1,1) PRIMARY KEY,
                          ContactName Char(7000),
                          Salario     Numeric(18,2));
GO

INSERT INTO Funcionarios(ContactName, Salario) VALUES('Fabiano', 1900)
INSERT INTO Funcionarios(ContactName, Salario) VALUES('Luciano',2050)
INSERT INTO Funcionarios(ContactName, Salario) VALUES('Gilberto', 2070)
INSERT INTO Funcionarios(ContactName, Salario) VALUES('Ivan', 2090)
GO

CREATE NONCLUSTERED INDEX ix_Salario ON Funcionarios(Salario)
GO

-- Halloween problem, will update all rows until then reach 3000 !!!
UPDATE Funcionarios SET Salario = Salario * 1.1
  FROM Funcionarios WITH(index=ix_Salario)
 WHERE Salario < 3000
GO

-- Simulate here with all different CURSOR declarations
-- DYNAMIC update the rows until all of then reach 3000
-- FAST_FORWARD update the rows until all of then reach 3000
-- STATIC update the rows only one time. 

BEGIN TRAN
DECLARE @ID INT
DECLARE TMP_Cursor CURSOR DYNAMIC 
--DECLARE TMP_Cursor CURSOR FAST_FORWARD
--DECLARE TMP_Cursor CURSOR STATIC READ_ONLY FORWARD_ONLY
    FOR SELECT ID 
          FROM Funcionarios WITH(index=ix_Salario)
         WHERE Salario < 3000

OPEN TMP_Cursor

FETCH NEXT FROM TMP_Cursor INTO @ID

WHILE @@FETCH_STATUS = 0
BEGIN
  SELECT * FROM Funcionarios WITH(index=ix_Salario)

  UPDATE Funcionarios SET Salario = Salario * 1.1 
   WHERE ID = @ID

  FETCH NEXT FROM TMP_Cursor INTO @ID
END

CLOSE TMP_Cursor
DEALLOCATE TMP_Cursor

SELECT * FROM Funcionarios

ROLLBACK TRAN
GO

Le persone evitano il cursore perché generalmente sono più difficili da scrivere rispetto a un semplice ciclo while, tuttavia, un ciclo while può essere costoso perché si selezionano costantemente dati da una tabella, temporanea o meno.

Con un cursore di sola lettura in avanzamento veloce, i dati vengono mantenuti in memoria ed è stato appositamente progettato per il looping.

Questo articolo evidenzia che un cursore medio viene eseguito 50 volte più velocemente di un ciclo while.

Alcune alternative all'utilizzo del cursore:

Mentre le tabelle di derivazione tablo-tablo-tabloops di tempetti di detenzione di casi associati a più interrogatori spesso, le operazioni del cursore possono essere ottenute anche con tecniche non corsor.

Se si è sicuri che sia necessario utilizzare il cursore, è necessario ridurre il più possibile il numero di record da elaborare.Un modo per farlo è fare in modo che i record vengano elaborati prima in una tabella temporanea, non nella tabella originale, ma in un cursore che utilizzerà i record nella tabella temporanea.Quando viene utilizzato questo percorso, si presuppone che il numero di record nella tabella temporanea sia stato notevolmente ridotto rispetto alla tabella originale.Con meno record, il cursore si completa più velocemente.

Alcune proprietà del cursore che influiscono sulle prestazioni includono:

AVANTI_SOLO:Supporta l'inoltro solo del cursore dalla prima riga alla fine con FETCH NEXT.A meno che non sia impostata come KEYSET o STATIC, la clausola SELECT viene rivalutata quando viene chiamato ciascun fetch.

STATICO:Crea una copia temporanea dei dati creati e viene utilizzata dal cursore.Ciò impedisce che il cursore venga ricalcolato ogni volta che viene chiamato, migliorando le prestazioni.Ciò non consente la modifica del tipo di cursore e le modifiche alla tabella non vengono riflesse quando viene chiamato il recupero.

MAZZO DI CHIAVI:Le righe con il cursore vengono inserite in una tabella in tempdb e le modifiche alle colonne non chiave vengono riflesse quando viene chiamato il recupero.Tuttavia, i nuovi record aggiunti alla tabella non vengono riflessi.Con il cursore keyset, l'istruzione SELECT non viene valutata nuovamente.

DINAMICO:Tutte le modifiche alla tabella si riflettono nel cursore.Il cursore viene rivalutato quando viene chiamato ogni recupero.Utilizza molte risorse e influisce negativamente sulle prestazioni.

AVANTI VELOCE:Il cursore è unidirezionale, come FORWARD_ONLY, ma specifica il cursore come di sola lettura.FORWARD_ONLY rappresenta un aumento delle prestazioni e il cursore non viene rivalutato a ogni recupero.Fornisce le migliori prestazioni se è adatto alla programmazione.

OTTIMISTA:Questa opzione può essere utilizzata per aggiornare le righe nel cursore.Se una riga viene recuperata e aggiornata e un'altra riga viene aggiornata tra le operazioni di recupero e aggiornamento, l'operazione di aggiornamento del cursore non riesce.Se viene utilizzato un cursore OPTIMISTIC in grado di eseguire l'aggiornamento della riga, non dovrebbe essere aggiornato da un altro processo.

NOTA:Se il cursore non è specificato, il valore predefinito è FORWARD_ONLY.

Per rispondere alle domande originali di Mile...

I cursori statici di avanzamento veloce, di sola lettura (affettuosamente noti come "cursore manichetta antincendio") sono in genere veloci o più veloci di una tabella temporanea equivalente e di un ciclo while perché tale cursore non è altro che una tabella temporanea e un ciclo while che è stato ottimizzato un po' dietro le quinte.

Per aggiungere a ciò che Eric Z.Beard ha pubblicato questo thread e per rispondere ulteriormente alla domanda di...

"Non parlano di non usare i cursori sull'evitare l'uso dei cursori quando sono disponibili approcci basati su set e l'uso di cursori aggiornabili ecc."

SÌ.Con pochissime eccezioni, sono necessari meno tempo e meno codice per scrivere un codice basato su set adeguato per fare la stessa cosa della maggior parte dei cursori e ha l'ulteriore vantaggio di utilizzare molte meno risorse e di solito viene eseguito MOLTO più velocemente di un cursore o di un ciclo While.In generale e con l'eccezione di alcune attività amministrative, dovrebbero davvero essere evitati a favore di un codice basato su set scritto correttamente.Ci sono, ovviamente, eccezioni a ogni "regola" ma, nel caso dei cursori, dei cicli While e di altre forme di RBAR, la maggior parte delle persone può contare le eccezioni su una mano senza usare tutte le dita.;-)

Esiste anche il concetto di "RBAR nascosto".Questo è un codice che sembra basato su set ma in realtà non lo è.Questo tipo di codice "basato su set" è il motivo per cui alcune persone hanno abbracciato i metodi RBAR e dicono che sono "OK".Ad esempio, risolvere il problema del totale parziale utilizzando una sottoquery correlata aggregata (SUM) con una disuguaglianza al suo interno per creare il totale parziale non è realmente basato su set nel mio libro.Invece è RBAR sotto steroidi perché, per ogni riga calcolata, deve "toccare" ripetutamente molte altre righe al ritmo di N*(N+1)/2.Questo è noto come "Triangular Join" ed è almeno la metà peggiore di un cartesiano completo (Cross Join o "Square Join").

Sebbene MS abbia apportato alcuni miglioramenti al funzionamento dei cursori a partire da SQL Server 2005, il termine "Cursore veloce" è ancora un ossimoro rispetto al codice basato su set scritto correttamente.Ciò vale anche per Oracle.Ho lavorato con Oracle per soli 3 anni in passato, ma il mio compito era apportare miglioramenti alle prestazioni del codice esistente.La maggior parte dei miglioramenti davvero sostanziali sono stati realizzati quando ho convertito i cursori in codice basato su set.Molti lavori che in precedenza richiedevano dalle 4 alle 8 ore per essere eseguiti sono stati ridotti a minuti e, talvolta, secondi.

Se desideri un cursore ancora più veloce di AVANTI VELOCE, utilizza un cursore STATICO.Sono più veloci di FAST FORWARD.Non estremamente più veloce ma può fare la differenza.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top