2010-07-19 8 views
8

Sto lavorando a un componente .NET che ottiene un set di dati dal database, esegue alcune logiche di business su quel set di dati e quindi aggiorna i singoli record nel database tramite una stored procedure simile a spUpdateOrderDetailDiscountedItem.Qual è una buona alternativa all'attivazione di una stored procedure 368 volte per aggiornare il database?

Per insiemi di dati di piccole dimensioni, questo non è un problema, ma quando avevo un insieme di dati molto grande che richiedeva un'iterazione di 368 chiamate di processo memorizzate per aggiornare i record nel database, mi sono reso conto che avevo un problema . Uno sviluppatore senior ha esaminato il mio codice proc memorizzato e ha detto che sembrava soddisfacente, ma ora mi piacerebbe esplorare un metodo migliore per inviare dati "batch" al database.

Quali opzioni sono disponibili per l'aggiornamento del database in batch? È possibile con i proc memorizzati? Quali altre opzioni ho?

Non avrò la possibilità di installare un ORM completo, ma qualsiasi consiglio è apprezzato.


Sfondo Ulteriori informazioni:

Il nostro modello di accesso ai dati attuali è stato costruito 5 anni fa e tutte le chiamate al db attualmente vengono eseguiti attraverso funzioni modulari/statici con nomi come ExecQuery e GetDataTable. Non sono sicuro di essere richiesto per rimanere all'interno di quel modello, ma dovrei fornire un'ottima giustificazione per andare al di fuori del nostro DAL corrente per arrivare al DB.

Vale anche la pena notare che sono abbastanza nuovo quando si tratta di operazioni CRUD e del database. Preferisco di gran lunga giocare/lavorare nel lato .NET del codice, ma i dati devono essere memorizzati da qualche parte, giusto?


memorizzati contenuti Proc:

ALTER PROCEDURE [dbo].[spUpdateOrderDetailDiscountedItem] 
    -- Add the parameters for the stored procedure here 
    @OrderDetailID decimal = 0, 
    @Discount money = 0, 
    @ExtPrice money = 0, 
    @LineDiscountTypeID int = 0, 
    @OrdersID decimal = 0, 
    @QuantityDiscounted money = 0, 
    @UpdateOrderHeader int = 0, 
    @PromoCode varchar(6) = '', 
    @TotalDiscount money = 0 

AS 
BEGIN 
    -- SET NOCOUNT ON added to prevent extra result sets from 
    -- interfering with SELECT statements. 
    SET NOCOUNT ON; 

    -- Insert statements for procedure here 
    Update OrderDetail 
    Set Discount = @Discount, ExtPrice = @ExtPrice, LineDiscountTypeID = @LineDiscountTypeID, LineDiscountPercent = @QuantityDiscounted 
    From OrderDetail with (nolock) 
    Where OrderDetailID = @OrderDetailID 

    if @UpdateOrderHeader = -1 
     Begin 
     --This code should get code the last time this query is executed, but only then. 
     exec spUpdateOrdersHeaderForSkuGroupSourceCode @OrdersID, 7, 0, @PromoCode, @TotalDiscount 
     End 
+8

368 operazioni non è molto. – Fosco

+0

È necessario visualizzare il contenuto della procedura memorizzata, anche se astratto ... –

+2

Quasi tutti gli ORM effettuano ancora 368 chiamate al database. Si limitano a raggrupparle. – NotMe

risposta

9

Un modo semplice e alternativo che ho visto in uso è quello di creare un'istruzione SQL composta da sql_execs che chiama lo sproc con i parametri nella stringa. Non è sicuro se ciò sia consigliato o no, ma dal punto di vista di .NET, si sta compilando solo uno SqlCommand e chiamando ExecuteNonQuery una volta ...

Nota se si sceglie questo, per favore, si prega di utilizzare il StringBuilder! :-)

Aggiornamento: preferisco di gran lunga Chris Lively risposta, non sapeva sui parametri con valori di tabella fino ad ora ... purtroppo l'OP sta usando 2005.

+0

Questa è probabilmente la soluzione meno invasiva. +1 – Chris

+0

@Chris L'ho visto utilizzato per eseguire il batch di più comandi del database in modo relativamente pulito dal codice - si ottengono comunque i vantaggi di sprocs, ma è necessaria una piccola logica aggiuntiva per codificare i parametri nella stringa in modo valido. Non è la mia rotta preferita, ma comprensibile. –

+1

non pensi che ti devi preoccupare delle query parametrizzate se tutto ciò che stai facendo è chiamare procs in quanto hanno comunque il loro piano di query? –

3

È possibile inviare l'intera serie di dati come input XML per la stored procedure. Quindi è possibile eseguire Imposta operazioni per modificare il database. Set based batterà gli RBAR sulle prestazioni quasi ogni volta.

+0

Grazie per la risposta. Cos'è un RBAR? –

+0

@ Ben: fila per agonizzante riga. –

+0

Ha. Sì, riassume perfettamente la situazione. –

0

possono creare batch dichiarazione con 368 chiama al tuo proc, quindi almeno non avrai 368 round trip. cioè pseudo codice

var lotsOfCommands = "spUpdateOrderDetailDiscountedItem 1; spUpdateOrderDetailDiscountedItem 2;spUpdateOrderDetailDiscountedItem ... 368' 

var new sqlcommand(lotsOfCommands) 
command.CommandType = CommandType.Text; 

//execute command 
+0

Questo non risolve nulla e si basa sul codice per evitare valori, ecc correttamente, il che è male. Supponendo una connessione al database persistente, il tempo necessario per inviare il comando dovrebbe essere minimo. Mentre tecnicamente si combinano tutti gli aggiornamenti in un unico batch inserendoli in una singola istruzione SQL, non si risolve il problema di chiamare uno sproc 368 volte (cosa ancora valida per l'SQL concatenato!). Inoltre, questo rimuove la capacità di tenere traccia degli errori su base per aggiornamento. –

+0

Beh, mi dispiace, ma pensi davvero che ridurre 368 round trip a uno non aumenti le prestazioni? È anche il cambiamento meno invasivo. Apprezzo che dovrai sfuggire ai valori. Sì, l'intero batch genera un errore piuttosto che un'esecuzione di proc, a seconda della semantica di cosa è stato fatto, potresti voler eseguire il rollback dell'intero lotto, che è ancora possibile con il batching. Non ho usato SQLCLR, quindi non posso commentare, da quello che ho letto su di esso, questo scenario potrebbe essere un uso ragionevole per questo. –

16

Se si utilizza SQL 2008, quindi è possibile utilizzare un table-valued parameter per spingere tutti gli aggiornamenti in una sola chiamata s'proc.

aggiornamento Per inciso, stiamo usando questo in combinazione con l'affermazione merge. In questo modo SQL Server si preoccupa di capire se stiamo inserendo nuovi record o aggiornando quelli esistenti. Questo meccanismo viene utilizzato in diverse sedi principali della nostra app Web e gestisce centinaia di modifiche alla volta. Durante il caricamento regolare vedremo questo proc richiamato circa 50 volte al secondo ed è MOLTO più veloce di qualsiasi altro modo che abbiamo trovato ... e sicuramente molto MOLTO più economico rispetto all'acquisto di server DB più grandi.

+1

Link di supporto: http://msdn.microsoft.com/en-us/library/bb675163.aspx –

+0

Non ne avevo mai sentito parlare prima. Grazie per il suggerimento. Potresti mostrare o collegare ad un esempio di come potresti usarlo in codice sia sul client che nella definizione del proc archiviato? –

+0

@Adam mi hai battuto per questo! Grazie! –

0

Ho avuto problemi quando provavo per la stessa cosa (tramite inserti, aggiornamenti, qualunque cosa). Durante l'utilizzo di un OleDbCommand con parametri, ci è voluto un po 'di tempo per ricreare costantemente l'oggetto e i parametri ogni volta che l'ho chiamato. Quindi, ho creato una proprietà sul mio oggetto per gestire tale chiamata e aggiunto anche i "parametri" appropriati alla funzione. Poi, quando avevo bisogno di chiamarlo/eseguirlo, passavo in loop ogni parametro nell'oggetto, lo impostavo in qualunque cosa avessi bisogno di essere, quindi lo eseguivo. Questo ha creato notevole miglioramento delle prestazioni ... tale pseudo-codice di mia operazione:

protected OleDbCommand oSQLInsert = new OleDbCommand(); 
// the "?" are place-holders for parameters... can be named parameters, 
// just for visual purposes 
oSQLInsert.CommandText = "insert into MyTable (fld1, fld2, fld3) values (?, ?, ?)"; 
// Now, add the parameters 
OleDbParameter NewParm = new OleDbParameter("parmFld1", 0); 
oSQLInsert.Parameters.Add(NewParm); 

NewParm = new OleDbParameter("parmFld2", "something"); 
oSQLInsert.Parameters.Add(NewParm); 

NewParm = new OleDbParameter("parmFld3", 0); 
oSQLInsert.Parameters.Add(NewParm); 

Ora, il comando SQL, e posto i titolari per la chiamata sono tutti pronti ad andare ... Poi, quando sono pronto a chiamarlo, vorrei fare qualcosa di simile ..

oSQLInsert.Parameters[0].Value = 123; 
oSQLInsert.Parameters[1].Value = "New Value"; 
oSQLInsert.Parameters[2].Value = 3; 

Quindi, basta eseguirlo. La ripetizione di centinaia di chiamate potrebbe essere eliminata dal tempo creando i comandi più e più volte ...

buona fortuna.

+0

Effettuerò questo attraverso un profilo per assicurarmi che il costruttore e la raccolta siano davvero ciò che sta mangiando il tempo. –

0

È un'azione una tantum (come "importa solo quei 368 nuovi clienti una volta") o devi regolarmente effettuare 368 chiamate sproc?

Se si tratta di un'azione una tantum, basta andare con le chiamate 368.
(se lo sproc fa molto di più degli aggiornamenti ed è probabile che trascini giù lo spettacolo, eseguirlo di sera o di notte o quando nessuno sta funzionando).

IMO, l'ottimizzazione prematura delle chiamate al database per le azioni una tantum non vale il tempo speso con esso.

1

se si utilizza una versione di SQL Server precedente fino al 2008, puoi spostare il tuo codice interamente nella stored procedure stessa.

Ci sono cose buone e "cattive" a riguardo.
Buono

  • Non c'è bisogno di tirare i dati attraverso un cavo di rete.
  • più veloce se la logica è impostato in base
  • Bilancia fino

Bad

  • Se avete regole contro qualsiasi logica nel database, si potrebbe infrangere il vostro disegno.
  • Se la logica non può essere impostato in base allora si potrebbe finire con un diverso insieme di problemi di prestazioni
  • Se si dispone di dipendenze esterne, questo potrebbe aumentare la difficoltà.

Senza dettagli su esattamente quali operazioni si stanno eseguendo sui dati, è difficile dare una raccomandazione solida.

UPDATE
Ben chiesto che cosa volevo dire in uno dei miei commenti su CLR e SQL Server. Leggi Using CLR Integration in SQL Server 2005. L'idea di base è che è possibile scrivere codice .Net per manipolare i dati e farli risiedere all'interno del server SQL stesso. Ciò ti evita di dover leggere tutti i dati attraverso la rete e inviare gli aggiornamenti in questo modo.

Il codice è richiamabile dai tuoi proc esistenti e ti dà tutta la potenza di .net in modo da non dover fare cose come i cursori. SQL resterà impostato in base al fatto che il codice .net può eseguire operazioni su singoli record.

Per inciso, questo è come le cose come heirarchyid sono state implementate in SQL 2008.

L'unico vero problema è che alcuni DBA non piace introdurre il codice dello sviluppatore come questo nel server di database. Quindi, a seconda dell'ambiente, questa potrebbe non essere un'opzione. Tuttavia, se lo è, allora è un modo molto potente per prendersi cura del proprio problema lasciando i dati e l'elaborazione all'interno del server del database.

+0

@Chris si evidenzia un buon punto sul possibile spostamento del codice interamente nel processo memorizzato. Il problema è che la logica di "auto-discounting" è un po 'complicata e richiederebbe quasi certamente l'uso di cursori nel proc memorizzato. Mi sembra più difficile mantenerlo dal punto di vista del codice. –

+0

@Ben McCormack: In tal caso è ancora disponibile un'altra opzione disponibile in determinate versioni del server SQL: CLR. Ma ora stiamo calpestando la terra che la maggior parte della gente sta lontana da ... con una buona ragione. ;) – NotMe

+0

@Chris cosa dici quando dici che CLR è un'opzione quando si lavora con SQL Server? Ovviamente, CLR * è * il motore che esegue il codice gestito .NET, ma che cosa ha a che fare con SQL Server? –

0

I parametri con valori di tabella sono i migliori, ma poiché si è su SQL 05, è possibile utilizzare la classe SqlBulkCopy per inserire batch di record. Nella mia esperienza, questo è molto veloce.

Problemi correlati