Domanda

Conoscevo alcuni motivi di prestazioni nei giorni 7 di SQL, ma gli stessi problemi esistono ancora in SQL Server 2005?Se ho un set di risultati in una procedura memorizzata su cui voglio agire individualmente, i cursori sono ancora una cattiva scelta?Se sì, perché?

È stato utile?

Soluzione

Perché i cursori occupano memoria e creano blocchi.

Ciò che stai realmente facendo è tentare di forzare la tecnologia basata su set in funzionalità non basate su set.E, in tutta onestà, dovrei sottolineare che cursors Fare hanno un uso, ma sono disapprovati perché molte persone che non sono abituate a usare soluzioni basate su set usano i cursori invece di capire la soluzione basata su set.

Ma, quando apri un cursore, stai sostanzialmente caricando quelle righe in memoria e bloccandole, creando potenziali blocchi.Quindi, mentre scorri il cursore, stai apportando modifiche ad altre tabelle e mantenendo aperti tutta la memoria e i blocchi del cursore.

Tutto ciò può potenzialmente causare problemi di prestazioni ad altri utenti.

Quindi, come regola generale, i cursori sono disapprovati.Soprattutto se questa è la prima soluzione a cui si arriva per risolvere un problema.

Altri suggerimenti

I commenti precedenti sul fatto che SQL sia un ambiente basato su set sono tutti veri.Tuttavia ci sono momenti in cui le operazioni riga per riga sono utili.Considera una combinazione di metadati e Dynamic SQL.

Come esempio molto semplice, supponiamo che io abbia più di 100 record in una tabella che definiscono i nomi delle tabelle che voglio copiare/troncare/qualunque cosa.Qual è il migliore?Hardcoding dell'SQL per fare ciò di cui ho bisogno?Oppure scorrere questo set di risultati e utilizzare Dynamic-SQL (sp_executesql) per eseguire le operazioni?

Non è possibile raggiungere l'obiettivo di cui sopra utilizzando SQL basato su set.

Quindi, usare i cursori o un ciclo while (pseudo-cursori)?

I cursori SQL vanno bene purché si utilizzino le opzioni corrette:

INSENSITIVE creerà una copia temporanea del set di risultati (evitandoti di doverlo fare tu stesso per il tuo pseudo-cursore).

READ_ONLY farà in modo che non vengano mantenuti blocchi sul set di risultati sottostante.Le modifiche nel set di risultati sottostante si rifletteranno nei recuperi successivi (come se si ottenesse TOP 1 dal tuo pseudo-cursore).

FAST_FORWARD creerà un cursore ottimizzato di sola lettura e di sola inoltro.

Leggi le opzioni disponibili prima di considerare tutti i cursori come malvagi.

C'è una soluzione ai cursori che utilizzo ogni volta che ne ho bisogno.

Creo una variabile di tabella con una colonna identità al suo interno.

inserisci tutti i dati con cui ho bisogno di lavorare al suo interno.

Quindi crea un blocco while con una variabile contatore e seleziona i dati che desidero dalla variabile tabella con un'istruzione select in cui la colonna identità corrisponde al contatore.

In questo modo non blocco nulla e utilizzo molta meno memoria ed è sicuro, non perderò nulla con un danneggiamento della memoria o qualcosa del genere.

E il codice del blocco è facile da vedere e gestire.

Questo è un semplice esempio:

DECLARE @TAB TABLE(ID INT IDENTITY, COLUMN1 VARCHAR(10), COLUMN2 VARCHAR(10))

DECLARE @COUNT INT,
        @MAX INT, 
        @CONCAT VARCHAR(MAX), 
        @COLUMN1 VARCHAR(10), 
        @COLUMN2 VARCHAR(10)

SET @COUNT = 1

INSERT INTO @TAB VALUES('TE1S', 'TE21')
INSERT INTO @TAB VALUES('TE1S', 'TE22')
INSERT INTO @TAB VALUES('TE1S', 'TE23')
INSERT INTO @TAB VALUES('TE1S', 'TE24')
INSERT INTO @TAB VALUES('TE1S', 'TE25')

SELECT @MAX = @@IDENTITY

WHILE @COUNT <= @MAX BEGIN
    SELECT @COLUMN1 = COLUMN1, @COLUMN2 = COLUMN2 FROM @TAB WHERE ID = @COUNT

    IF @CONCAT IS NULL BEGIN
        SET @CONCAT = '' 
    END ELSE BEGIN 
        SET @CONCAT = @CONCAT + ',' 
    END

    SET @CONCAT = @CONCAT + @COLUMN1 + @COLUMN2

    SET @COUNT = @COUNT + 1
END

SELECT @CONCAT

Penso che i cursori abbiano una brutta reputazione perché i principianti di SQL li scoprono e pensano "Ehi, un ciclo for!Quelli li so usare!" e poi continuano a usarli per qualsiasi cosa.

Se li usi per quello per cui sono progettati, non posso trovare difetti in questo.

SQL è un linguaggio basato su set: è ciò che sa fare meglio.

Penso che i cursori siano ancora una cattiva scelta a meno che tu non ne capisca abbastanza da giustificarne l'uso in circostanze limitate.

Un altro motivo per cui non mi piacciono i cursori è la chiarezza.Il blocco cursore è così brutto che è difficile usarlo in modo chiaro ed efficace.

Tutto ciò che è stato detto, ecco Sono alcuni casi in cui un cursore è davvero la soluzione migliore: semplicemente non sono i casi per cui i principianti desiderano utilizzarlo.

A volte la natura dell'elaborazione che è necessario eseguire richiede cursori, sebbene per motivi di prestazioni sia sempre meglio scrivere le operazioni utilizzando la logica basata su set, se possibile.

Non definirei "cattiva pratica" l'utilizzo dei cursori, ma consumano più risorse sul server (rispetto a un approccio equivalente basato su set) e il più delle volte non sono necessari.Detto questo, il mio consiglio sarebbe di considerare altre opzioni prima di ricorrere a un cursore.

Esistono diversi tipi di cursori (solo inoltro, statici, keyset, dinamici).Ognuno ha caratteristiche prestazionali diverse e costi generali associati.Assicurati di utilizzare il tipo di cursore corretto per la tua operazione.Solo inoltro è l'impostazione predefinita.

Un argomento a favore dell'utilizzo di un cursore è quando è necessario elaborare e aggiornare singole righe, soprattutto per un set di dati che non dispone di una chiave univoca valida.In tal caso è possibile utilizzare la clausola FOR UPDATE quando si dichiara il cursore ed elaborare gli aggiornamenti con UPDATE...DOVE CORRENTE DI.

Si noti che i cursori "lato server" erano popolari (da ODBC e OLE DB), ma ADO.NET non li supporta e AFAIK non lo farà mai.

@ Daniel P -> non è necessario utilizzare un cursore per farlo.Puoi facilmente utilizzare la teoria basata sugli insiemi per farlo.Per esempio:con SQL 2008

DECLARE @commandname NVARCHAR(1000) = '';

SELECT @commandname += 'truncate table ' + tablename + '; ';
FROM tableNames;

EXEC sp_executesql @commandname;

farà semplicemente quello che hai detto sopra.E puoi fare lo stesso con Sql 2000 ma la sintassi della query sarebbe diversa.

Tuttavia, il mio consiglio è di evitare il più possibile i cursori.

Gayam

Sono pochissimi i casi in cui l'uso di un cursore è giustificato.Non ci sono quasi casi in cui supererà le prestazioni di una query relazionale basata su set.A volte è più semplice per un programmatore pensare in termini di cicli, ma l'uso della logica di insieme, ad esempio per aggiornare un gran numero di righe in una tabella, si tradurrà in una soluzione che non consiste solo in molte meno righe di codice SQL, ma questo funziona molto più velocemente, spesso diversi ordini di grandezza Più veloce.

Anche il cursore di avanzamento rapido in SQL Server 2005 non può competere con le query basate su set.Il grafico del degrado delle prestazioni spesso inizia ad assomigliare a un'operazione n ^ 2 rispetto a quella basata su set, che tende a essere più lineare man mano che il set di dati diventa molto grande.

I cursori hanno il loro posto, tuttavia penso che sia principalmente perché vengono spesso utilizzati quando una singola istruzione select sarebbe sufficiente per fornire aggregazione e filtraggio dei risultati.

Evitare i cursori consente a SQL Server di ottimizzare in modo più completo le prestazioni della query, cosa molto importante nei sistemi più grandi.

I cursori di solito non sono la malattia, ma un suo sintomo:non utilizzare l'approccio basato su set (come menzionato nelle altre risposte).

Non comprendere questo problema e credere semplicemente che evitare il cursore "malvagio" lo risolverà, può peggiorare le cose.

Ad esempio, sostituendo l'iterazione del cursore con altro codice iterativo, come lo spostamento dei dati su tabelle temporanee o variabili di tabella, per eseguire il loop sulle righe in un modo simile:

SELECT * FROM @temptable WHERE Id=@counter 

O

SELECT TOP 1 * FROM @temptable WHERE Id>@lastId

Un simile approccio, come mostrato nel codice di un'altra risposta, peggiora le cose molto e non risolve il problema originale.È un anti-modello chiamato programmazione del culto del carico:non sapere PERCHÉ qualcosa va male e quindi implementare qualcosa di peggio per evitarlo!Recentemente ho modificato tale codice (utilizzando un #temptable e nessun indice su identità/PK) in un cursore e l'aggiornamento di poco più di 10000 righe ha richiesto solo 1 secondo invece di quasi 3 minuti.Manca ancora un approccio basato sul set (essendo il male minore), ma il meglio che potevo fare in quel momento.

Un altro sintomo di questa mancanza di comprensione può essere quella che a volte chiamo "malattia di un oggetto":applicazioni di database che gestiscono singoli oggetti tramite livelli di accesso ai dati o mappatori relazionali a oggetti.In genere codice come:

var items = new List<Item>();
foreach(int oneId in itemIds)
{
    items.Add(dataAccess.GetItemById(oneId);
}

invece di

var items = dataAccess.GetItemsByIds(itemIds);

Il primo di solito inonda il database con tonnellate di SELECT, un viaggio di andata e ritorno per ciascuno, specialmente quando entrano in gioco alberi/grafici di oggetti e si verifica il famigerato problema SELECT N+1.

Questo è il lato applicativo della non comprensione dei database relazionali e dell'approccio basato su set, proprio come lo sono i cursori quando si utilizza il codice del database procedurale, come T-SQL o PL/SQL!

Il problema fondamentale, credo, è che i database sono progettati e ottimizzati per operazioni basate su set: seleziona, aggiorna ed elimina grandi quantità di dati in un unico rapido passaggio basato sulle relazioni tra i dati.

Il software in memoria, d'altra parte, è progettato per operazioni individuali, quindi eseguire il looping su un set di dati e potenzialmente eseguire diverse operazioni su ciascun elemento in serie è ciò che sa fare meglio.

Il loop non è ciò per cui sono progettati il ​​database o l'architettura di archiviazione e, anche in SQL Server 2005, non otterrai prestazioni vicine a quelle ottenute se inserisci i dati di base impostati in un programma personalizzato ed esegui il loop in memoria , utilizzando oggetti/strutture di dati il ​​più leggeri possibile.

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