2008-08-23 7 views
29

Quando si scrivono query di database in qualcosa come TSQL o PLSQL, spesso si ha la possibilità di eseguire iterazioni su righe con un cursore per eseguire l'attività o creare una singola istruzione SQL che faccia lo stesso lavoro tutto in una volta.Perché le query relazionali basate su set sono migliori dei cursori?

Inoltre, abbiamo la possibilità di inserire semplicemente una grande serie di dati nella nostra applicazione e quindi elaborarli riga per riga, con C# o Java o PHP o altro.

Perché è meglio utilizzare query basate su set? Qual è la teoria dietro questa scelta? Qual è un buon esempio di una soluzione basata su cursore e il suo equivalente relazionale?

risposta

15

Il motivo principale di cui sono a conoscenza è che le operazioni basate su set possono essere ottimizzate dal motore eseguendole su più thread. Ad esempio, pensa a un quicksort: puoi separare la lista che stai ordinando in più "blocchi" e ordinarli separatamente nel loro thread. I motori SQL possono fare cose simili con enormi quantità di dati in una query basata su set.

Quando si eseguono operazioni basate su cursore, il motore può essere eseguito solo in sequenza e l'operazione deve essere a thread singolo.

0

L'idea alla base del preferire il lavoro nelle query è che il motore di database può ottimizzare riformulandolo. Questo è anche il motivo per cui vorresti eseguire EXPLAIN sulla tua query, per vedere cosa sta facendo il db in realtà. (ad esempio, approfittando di indici, dimensioni delle tabelle e talvolta conoscenza delle distribuzioni di valori nelle colonne)

Detto questo, per ottenere buone prestazioni nel tuo caso concreto concreto, potresti dover piegare o infrangere le regole.

Oh, un altro motivo potrebbe essere vincoli: Incrementare una colonna unica si potrebbe essere bene se i vincoli vengono controllati dopo tutto gli aggiornamenti, ma genera una collisione se fatto uno per uno.

0

set base è fatto in una sola operazione cursore come molte operazioni come il set di righe del cursore

12

query Set base sono (di solito) più veloce perché:

  1. Hanno più informazioni per Query Optimizer per ottimizzare
  2. Si può ammucchiare legge dal disco
  3. C'è meno la registrazione coinvolti per rollback, i registri delle transazioni, ecc
  4. Meno serrature sono prese, che diminuisce sovraccarico
  5. logica
  6. Set base è al centro di RDBMS, così che sono stati pesantemente ottimizzati per esso (spesso, a scapito delle prestazioni procedurale)

estrazione di dati ad il livello intermedio per elaborarlo può essere utile, tuttavia, poiché rimuove il sovraccarico di elaborazione dal server DB (che è la cosa più difficile da ridimensionare, e normalmente fa anche altre cose). Inoltre, normalmente non hai gli stessi overhead (o benefici) nel livello intermedio. Cose come la registrazione transazionale, il blocco e il blocco integrati, ecc. - a volte sono necessarie e utili, altre volte sono solo uno spreco di risorse.

Un semplice cursore con logica procedurale vs.set esempio a base (T-SQL) che assegnerà un prefisso basato sullo scambio telefonico:

--Cursor 
DECLARE @phoneNumber char(7) 
DECLARE c CURSOR LOCAL FAST_FORWARD FOR 
    SELECT PhoneNumber FROM Customer WHERE AreaCode IS NULL 
OPEN c 
FETCH NEXT FROM c INTO @phoneNumber 
WHILE @@FETCH_STATUS = 0 BEGIN 
    DECLARE @exchange char(3), @areaCode char(3) 
    SELECT @exchange = LEFT(@phoneNumber, 3) 

    SELECT @areaCode = AreaCode 
    FROM AreaCode_Exchange 
    WHERE Exchange = @exchange 

    IF @areaCode IS NOT NULL BEGIN 
     UPDATE Customer SET AreaCode = @areaCode 
     WHERE CURRENT OF c 
    END 
    FETCH NEXT FROM c INTO @phoneNumber 
END 
CLOSE c 
DEALLOCATE c 
END 

--Set 
UPDATE Customer SET 
    AreaCode = AreaCode_Exchange.AreaCode 
FROM Customer 
JOIN AreaCode_Exchange ON 
    LEFT(Customer.PhoneNumber, 3) = AreaCode_Exchange.Exchange 
WHERE 
    Customer.AreaCode IS NULL 
+0

'UPDATE SET clienti AreaCode = AreaCode_Exchange.AreaCode FROM Customer ISCRIVITI ON AreaCode_Exchange SINISTRA (Customer.PhoneNumber, 3) = AreaCode_Exchange.Exchange DOVE Customer.AreaCode IS NULL', si può spiegare questo uno' SINISTRA (Customer.PhoneNumber, 3) 'e la sua funzionalità – Smart003

2

ritengo la risposta reale è, come tutti gli approcci di programmazione, che dipende da quale è meglio. In generale, un linguaggio basato su set sarà più efficiente, perché è quello che è stato progettato per fare. Ci sono due luoghi dove un cursore è un vantaggio:

  1. si aggiorna un grande insieme di dati in un database in cui righe di bloccaggio non è accettabile (orario produzione forse). Un aggiornamento basato su set ha la possibilità di bloccare una tabella per diversi secondi (o minuti), in cui un cursore (se scritto correttamente) non lo fa. Il cursore può spostarsi tra le righe aggiornandosi una alla volta e non devi preoccuparti di influenzare qualcos'altro.

  2. Il vantaggio dell'utilizzo di SQL è che la maggior parte del lavoro per l'ottimizzazione viene gestito dal motore del database nella maggior parte dei casi. Con i motori db di classe enterprise i progettisti sono andati a lunghezze meticolose per assicurarsi che il sistema sia efficiente nella gestione dei dati. Lo svantaggio è che SQL è un linguaggio basato su set. Devi essere in grado di definire un insieme di dati per usarlo. Anche se sembra facile, in alcune circostanze non lo è. Una query può essere così complessa che gli ottimizzatori interni nel motore non possono creare efficacemente un percorso di esecuzione, e indovina cosa succede ... la tua scatola super potente con 32 processori usa un singolo thread per eseguire la query perché non sa come fare qualsiasi altra cosa, quindi sprechi tempo del processore sul server del database che generalmente è solo uno dei server delle applicazioni diversi da contrapposti (quindi torna alla ragione 1, ti imbatti in contese con altre cose che devono essere eseguite sul server database). Con un linguaggio basato su riga (C#, PHP, JAVA ecc.), Hai più controllo su ciò che accade. È possibile recuperare un set di dati e forzarlo a eseguire il modo in cui lo si desidera. (Separare i dati impostati per l'esecuzione su più thread, ecc.). La maggior parte delle volte, non sarà efficiente come eseguirlo sul motore del database, perché dovrà ancora accedere al motore per aggiornare la riga, ma quando si devono fare più di 1000 calcoli per aggiornare una riga (e diciamo che hai un milione di righe), un server di database può iniziare ad avere problemi.

15

In aggiunta a quanto sopra "Lasciate che i DBMS fare il lavoro" (che è una grande soluzione), ci sono un paio di altre buone ragioni per lasciare la query nel DBMS:

  • È (soggettivamente) più facile da leggere. Guardando il codice in un secondo momento, preferiresti provare e analizzare una complessa procedura memorizzata (o codice lato client) con loop e cose, o preferiresti guardare una dichiarazione SQL concisa?
  • Evita i viaggi di andata e ritorno della rete. Perché spingere tutti quei dati al client e poi spingere di più? Perché affliggere la rete se non è necessario?
  • È uno spreco. Il DBMS e il/i server delle app dovranno bufferizzare alcuni/tutti i dati per lavorarci. Se non si dispone di memoria infinita, è probabile che sfogli altri dati; perché buttare via dalla memoria forse cose importanti per tamponare un set di risultati che è per lo più inutile?
  • Perché non dovresti? Hai acquistato (o utilizzi in altro modo) un DBMS estremamente affidabile e molto veloce. Perché non dovresti usarlo?
+0

Sono d'accordo con Matt. Leggere alcuni libri di Joe Celko aiuta anche quando si prendono alcune di queste decisioni. –

+2

Hai dimenticato di menzionare l'ottimizzazione della query e la natura dichiarativa di SQL; i cursori e altri approcci basati su righe definiscono esattamente come recuperare/elaborare i dati, in cui le query SQL definiscono solo cosa fare: l'RDBMS è quindi libero di elaborare il piano migliore in base alle statistiche (ad esempio, a seconda dell'indice delle statistiche ricerca potrebbe essere un approccio peggiore o migliore dell'indice di scansione, RDBMS può fare una distinzione, gli approcci basati su riga non possono ...) – Unreason

6

Volevi alcuni esempi di vita reale. La mia azienda aveva un cursore che impiegava oltre 40 minuti per elaborare 30.000 record (e c'erano volte in cui avevo bisogno di aggiornare oltre 200.000 record). Ci sono voluti 45 secondi per fare lo stesso compito senza il cursore.In un altro caso ho rimosso un cursore e inviato il tempo di elaborazione da oltre 24 ore a meno di un minuto. Uno era un inserto che utilizzava la clausola values ​​invece di una select e l'altro era un aggiornamento che utilizzava variabili invece di un join. Una buona regola è che se si tratta di un inserimento, aggiornamento o eliminazione, è necessario cercare un modo basato su set per eseguire l'attività.

I cursori hanno i loro usi (o il codice non sarebbe il loro in primo luogo), ma dovrebbero essere estremamente rari quando si esegue una query su un database relazionale (ad eccezione di Oracle che è ottimizzato per utilizzarli). Un punto in cui possono essere più veloci è quando si eseguono i calcoli in base al valore del record precedente (totali correnti). BUt anche quello dovrebbe essere testato.

Un altro caso limitato di utilizzo di un cursore è l'elaborazione batch. Se si sta tentando di fare troppo in una volta, in modalità set-based si può bloccare la tabella ad altri utenti. Se hai un set veramente grande, potrebbe essere meglio suddividerlo in inserti, aggiornamenti o eliminazioni più piccoli basati su set che non terranno il blocco troppo lungo e poi scorreranno attraverso i set usando un cursore.

Un terzo utilizzo di un cursore è eseguire i proc memorizzati dal sistema attraverso un gruppo di valori di input. Detto questo è limitato ad un set generalmente piccolo e nessuno dovrebbe confondere con i proc del sistema, questa è una cosa accettabile da fare per un amministratore. Non consiglio di fare la stessa cosa con un proc creato da un utente per elaborare un grande batch e riutilizzare il codice. È preferibile scrivere una versione basata su set che risulterà più performante in quanto le prestazioni dovrebbero trionfare nel riutilizzo del codice nella maggior parte dei casi.

+0

Volevo aggiungere questo link: http://wiki.lessthandot.com/index.php/Cursors_and_How_to_Avoid_Them – HLGEM

1

Penso che si tratti di utilizzare il database è stato progettato per essere utilizzato. I server di database relazionali sono sviluppati e ottimizzati in modo specifico per rispondere meglio alle domande espresse nella logica dell'insieme.

Funzionalmente, la penalità per i cursori varierà enormemente da prodotto a prodotto. Alcuni (la maggior parte?) Rdbms sono costruiti almeno parzialmente sui motori isam. Se la domanda è appropriata e l'impiallacciatura sufficientemente sottile, potrebbe essere effettivamente efficiente utilizzare un cursore. Ma questa è una delle cose con cui dovresti familiarizzare intimamente, in termini della tua marca di dbms, prima di provarla.

1

Come è stato detto, il database è ottimizzato per le operazioni impostate. Gli ingegneri letteralmente si sono seduti e hanno effettuato il debug/tuning di quel database per lunghi periodi di tempo. Le possibilità di ottimizzarle sono piuttosto ridotte. Ci sono tutti i tipi di trucchi divertenti con i quali puoi giocare se hai un set di dati con cui lavorare insieme come letture/scritture di dischi batch, cache, multi-threading. Inoltre, alcune operazioni hanno un costo elevato, ma se lo si fa a un gruppo di dati contemporaneamente il costo per pezzo di dati è basso. Se lavori solo una riga alla volta, molti di questi metodi e operazioni non possono accadere.

Ad esempio, basta osservare il modo in cui il database si unisce. Guardando a spiegare i piani puoi vedere diversi modi di fare join. Molto probabilmente con un cursore si va riga per riga in una tabella e quindi si selezionano i valori necessari da un'altra tabella. Fondamentalmente è come un ciclo annidato solo senza la rigidità del loop (che è molto probabilmente compilato in linguaggio macchina e ottimizzato). SQL Server da solo ha un sacco di modi per unirsi. Se le righe sono ordinate, utilizzerà un qualche tipo di algoritmo di fusione, se una tabella è piccola, può trasformare una tabella in una tabella di ricerca hash e fare il join eseguendo ricerche O (1) da una tabella nella tabella di ricerca. Esistono numerose strategie di join che hanno molti DBMS che ti battono cercando i valori da una tabella in un cursore.

Basta guardare l'esempio della creazione di una tabella di ricerca hash. Per costruire la tabella è probabilmente m operazioni se si uniscono due tabelle una di lunghezza n e una di lunghezza m dove m è la tabella più piccola. Ogni ricerca deve essere costante, quindi sono n operazioni. quindi in pratica l'efficienza di un hash join è intorno a m (setup) + n (lookup). Se lo fai tu stesso e non hai ipotesi di ricerca/indice, per ognuna delle n righe dovrai cercare m record (in media equivale a m/2 ricerche).Quindi in pratica il livello delle operazioni va da m + n (unendo un gruppo di record contemporaneamente) a m * n/2 (facendo ricerche tramite un cursore). Anche le operazioni sono semplificazioni. A seconda del tipo di cursore, il recupero di ogni riga di un cursore può essere lo stesso di un'altra selezione dalla prima tabella.

Anche le serrature ti uccidono. Se si hanno cursori su un tavolo si stanno bloccando le righe (nel server SQL questo è meno grave per i cursori statici e forward_only ... ma la maggior parte del codice del cursore che vedo apre un cursore senza specificare alcuna di queste opzioni). Se esegui l'operazione in un set, le righe saranno comunque bloccate ma per un periodo di tempo minore. Anche l'ottimizzatore può vedere cosa stai facendo e potrebbe decidere che è più efficace bloccare l'intera tabella anziché un mucchio di righe o pagine. Ma se si va linea per linea l'ottimizzatore non ha idea.

L'altra cosa è che ho sentito dire che nel caso di Oracle è super ottimizzato per fare operazioni con il cursore in modo da non avvicinarsi alla stessa penalità per le operazioni basate sui set rispetto ai cursori in Oracle come in SQL Server. Non sono un esperto di Oracle quindi non posso dirlo con certezza. Ma più di una persona Oracle mi ha detto che i cursori sono molto più efficienti in Oracle. Quindi, se hai sacrificato tuo figlio primogenito per Oracle non si può avere a preoccuparsi di cursori, consultare il proprio locale DBA Oracle ben pagati :)

0

la vera risposta è andare a prendere uno dei libri E.F. Codd s' e spazzolare in su relational algebra. Quindi prendi un buon libro su Big O notation. IMHO, dopo circa vent'anni di IT, è una delle grandi tragedie del moderno grado MIS o CS: pochissimi studiano effettivamente il computo. Sai ... la parte "computa" del "computer"? Structured Query Language (e tutti i suoi superset) è semplicemente un'applicazione pratica dell'algebra relazionale. Sì, l'RDBMS ha ottimizzato la gestione della memoria e la lettura/scrittura, ma lo stesso si potrebbe dire per i linguaggi procedurali. Mentre lo leggevo, la domanda iniziale non riguarda l'IDE, il software, ma piuttosto l'efficienza di un metodo di calcolo rispetto all'altro.

Anche una rapida familiarizzazione con la notazione di Big O inizierà a far luce sul perché, quando si gestiscono insiemi di dati, l'iterazione è più costosa di una dichiarazione dichiarativa.

0

In poche parole, nella maggior parte dei casi è più facile e veloce lasciare che sia il database a farlo per te.

Lo scopo del database nella vita è archiviare/recuperare/manipolare i dati in formati prestabiliti e per essere veramente veloci. Il tuo codice VB.NET/ASP.NET probabilmente non è neanche lontanamente veloce quanto un motore di database dedicato. Sfruttare questo è un uso saggio delle risorse.

Problemi correlati