Qual è il senso e il vantaggio dell'utilizzo di SqlCommand.Prepare ()?


14

Mi sono imbattuto nel codice dello sviluppatore in cui il metodo SqlCommand.Prepare () (vedi MSDN) viene ampiamente utilizzato prima dell'esecuzione delle query SQL. E mi chiedo quale sia il vantaggio di questo?

Campione:

command.Prepare();
command.ExecuteNonQuery();
//...
command.Parameters[0].Value = 20;
command.ExecuteNonQuery();

Ho giocato un po 'e tracciato. L'esecuzione del comando dopo aver chiamato il Prepare()metodo fa eseguire a SQL Server la seguente istruzione:

declare @p1 int
set @p1=1
exec sp_prepexec @p1 output,N'@id int,@desc text',N'INSERT INTO dbo.testtable (id) VALUES (@id)',@id=20'
select @p1

Dopodiché quando il parametro ottiene il suo valore e SqlCommand.ExecuteNonQuery()viene chiamato, il seguente viene eseguito su Sql-Server:

exec sp_execute 1,@id=20

A me sembra che l'istruzione venga in qualche modo compilata non appena Prepare()viene eseguita. Mi chiedo quale sia il vantaggio di questo? Ciò significa che viene inserito nella cache del piano e può essere riutilizzato non appena la query finale viene eseguita con i valori dei parametri desiderati?

Ho capito (e documentato in un'altra domanda ) che i comandi Sql che vengono eseguiti con SqlParameters sono sempre racchiusi nelle sp_executesqlchiamate di procedura. Ciò rende Sql Server in grado di memorizzare e riutilizzare i piani in modo indipendente dai valori dei parametri.

Riguardo a questo mi chiedo se il prepare()metodo sia un po 'inutile o obsoleto o se mi manchi qualcosa qui?


Ho sempre pensato che avesse qualcosa a che fare con la prevenzione dell'iniezione SQL, quindi avresti preparato la query per accettare determinate variabili di determinati tipi. In questo modo se non passi variabili di quel tipo, la query fallisce
Mark Sinkinson,

Anche se nel dire che, questa spiegazione PHP è probabilmente altrettanto valido per .NET php.net/manual/en/pdo.prepared-statements.php
Mark Sinkinson

"Sì, signore. Autista, preparatevi ad uscire." "Che cosa ti prepari ?! Ti prepari sempre! Vai e basta!"
Jon of All Trades,

Risposte:


19

Preparare un batch SQL separatamente dall'esecuzione del batch SQL preparato è un costrutto efficacemente **inutile per SQL Server dato come vengono memorizzati nella cache i piani di esecuzione. Separare le fasi di preparazione (analisi, associazione di qualsiasi parametro e compilazione) ed esecuzione ha senso solo in assenza di memorizzazione nella cache. Lo scopo è quello di risparmiare tempo dedicato all'analisi e alla compilazione riutilizzando un piano esistente, ed è ciò che fa la cache del piano interno. Ma non tutti gli RDBMS eseguono questo livello di memorizzazione nella cache (chiaramente dall'esistenza di queste funzioni) e quindi è lasciato al codice client richiedere che un piano sia "memorizzato nella cache", quindi salvare l'ID cache e riutilizzarlo esso. Questo è un onere aggiuntivo per il codice client (e per il programmatore ricordarsi di farlo e farlo bene) che non è necessario. In effetti, la pagina MSDN per il metodo IDbCommand.Prepare () afferma:

Il server memorizza automaticamente nella cache i piani per il riutilizzo, se necessario; pertanto, non è necessario chiamare questo metodo direttamente nell'applicazione client.

Va notato che, per quanto riguarda i miei test (che corrispondono a ciò che mostri nella Domanda), chiamando SqlCommand.Prepare () , non esegue un'operazione "solo": chiama sp_prepexec che prepara ed esegue l'SQL ; non chiama sp_prepare che è solo analisi e compilazione (e non accetta alcun valore di parametro, ma solo i loro nomi e tipi di dati). Quindi, non ci può essere alcun vantaggio di "pre-compilazione" della chiamata SqlCommand.Preparepoiché esegue un'esecuzione immediata (supponendo che l'obiettivo sia ritardare l'esecuzione). TUTTAVIA , c'è un interessante potenziale vantaggio minore di chiamare SqlCommand.Prepare(), anche se chiama sp_prepexecinvece di sp_prepare. Si prega di consultare la nota (** ) in fondo per i dettagli.

I miei test mostrano che anche quando SqlCommand.Prepare()viene chiamato (e si noti che questa chiamata non emette alcun comando su SQL Server), il ExecuteNonQueryo ExecuteReaderche segue viene eseguito completamente e, se è ExecuteReader, restituisce righe. Tuttavia, SQL Server Profiler mostra che la prima SqlCommand.Execute______()chiamata dopo la SqlCommand.Prepare()chiamata viene registrata come evento "Prepara SQL" e le .Execute___()chiamate successive vengono registrate come eventi "Exec Prepared SQL".


Codice test

È stato caricato su PasteBin su: http://pastebin.com/Yc2Tfvup . Il codice crea un'app console .NET / C # che deve essere eseguita mentre è in esecuzione una traccia di SQL Server Profiler (o una sessione di eventi estesi). Si interrompe dopo ogni passaggio, quindi sarà chiaro quali istruzioni hanno un effetto particolare.


AGGIORNARE

Abbiamo trovato maggiori informazioni e un leggero potenziale motivo per evitare di chiamare SqlCommand.Prepare(). Una cosa che ho notato nei miei test e che cosa dovrebbe essere notato per chiunque esegua quell'app Console di test: non viene mai fatta una chiamata esplicita sp_unprepare. Ho fatto qualche ricerca e ho trovato il seguente post nei forum MSDN:

SqlCommand - Preparare o non preparare?

La risposta accettata contiene un sacco di informazioni, ma i punti salienti sono:

  • ** Ciò che ti prepara effettivamente ti salva è principalmente il tempo necessario per trasmettere la stringa di query sul filo.
  • Una query preparata non garantisce il salvataggio di un piano memorizzato nella cache e non è più veloce di una query ad hoc una volta raggiunta il server.
  • Gli RPC (CommandType.StoredProcedure) non ottengono nulla dalla preparazione.
  • Sul server, un handle preparato è fondamentalmente un indice in una mappa in memoria contenente TSQL da eseguire.
  • La mappa viene memorizzata in base alla connessione, non è possibile utilizzare la connessione incrociata.
  • Il reset inviato quando il client riutilizza la connessione dal pool cancella la mappa.

Ho anche trovato il seguente post del team SQLCAT, che sembra essere correlato, ma potrebbe semplicemente essere un problema specifico di ODBC mentre SqlClient si ripulisce correttamente dopo aver eliminato SqlConnection. È difficile da dire dal momento che il post SQLCAT non menziona alcun test aggiuntivo che possa aiutare a dimostrare questo come causa, come cancellare il pool di connessioni, ecc.

Fai attenzione a quelle istruzioni SQL preparate


CONCLUSIONE

Dato che:

  • l'unico vero vantaggio della chiamata SqlCommand.Prepare()sembra essere che non è necessario inviare nuovamente il testo della query sulla rete,
  • chiamare sp_preparee sp_prepexecarchiviare il testo della query come parte della memoria della connessione (ad es. memoria di SQL Server)

Vorrei raccomandare contro chiamando SqlCommand.Prepare()perché l'unico potenziale beneficio è salvare i pacchetti di rete, mentre il rovescio della medaglia sta prendendo più memoria del server. Sebbene la quantità di memoria consumata sia probabilmente molto ridotta nella maggior parte dei casi, la larghezza di banda della rete raramente è mai un problema poiché la maggior parte dei server DB è direttamente connessa ai server delle app con un minimo di 10 Megabit (più probabilmente 100 Megabit o Gigabit in questi giorni) ( e alcuni sono persino nella stessa scatola ;-). Quello e la memoria sono quasi sempre una risorsa più scarsa della larghezza di banda della rete.

Suppongo che, per chiunque scriva software in grado di connettersi a più database, la praticità di un'interfaccia standard potrebbe cambiare questa analisi costi / benefici. Ma anche in quel caso, devo credere che sia ancora abbastanza facile astrarre un'interfaccia DB "standard" che consenta diversi provider (e quindi differenze tra loro, come non dover chiamare Prepare()) dato che farlo è Basic Object Programmazione orientata che probabilmente stai già facendo nel codice dell'app ;-).


Grazie per questa ampia risposta. Ho testato i tuoi passaggi e ho avuto due aberrazioni dalle tue aspettative / risultati: nel passaggio 4 ho ottenuto un risultato aggiuntivo. Al punto 10 ho avuto un altro plan_handle rispetto al punto 2.
Magier

1
@Magier Non sei sicuro del passaggio 4 ... forse avevi diverse opzioni SET o in qualche modo il testo della query tra i due era diverso? Anche una differenza di singolo personaggio (visibile o no) sarà un piano diverso. Copia e incolla da questa pagina Web potrebbe non essere d'aiuto, quindi copia la query (tutto tra virgolette singole) dalla sp_preparealla chiamata sp_executesql, per essere sicuro. Per il passaggio 10, sì, ieri ho fatto più test e ho visto alcune occasioni con diversi handle per sp_prepare, ma comunque mai lo stesso sp_executesql. Proverò un'altra cosa e aggiornerò la mia risposta.
Solomon Rutzky,

1
@Magier In realtà, il test che ho eseguito prima lo ha coperto e non credo che ci sia un vero riutilizzo del piano, soprattutto alla luce del post del forum MSDN che dice che memorizza nella cache solo l'SQL. Sono ancora curioso di sapere come è in grado di riutilizzare plan_handle mentre sp_executesqlnon è possibile o meno. Ma a prescindere, il tempo di analisi è ancora sp_prepareattivo se il piano è stato rimosso dalla cache, quindi rimuoverò quella parte della mia risposta (interessante ma irrilevante). Quella parte era comunque una nota a margine interessante poiché non rispondeva alla tua domanda diretta. Tutto il resto vale ancora.
Solomon Rutzky,

1
Questo era il tipo di spiegazione che stavo cercando.
CSharpie

5

Riguardo a questo mi chiedo se il metodo apprendi () è in qualche modo inutile o obsoleto o se mi manca qualcosa qui?

Direi che il metodo Prepare ha un valore limitato nel mondo SqlCommand, non che sia del tutto inutile. Un vantaggio è che Prepare fa parte dell'interfaccia IDbCommand implementata da SqlCommand. Ciò consente l'esecuzione dello stesso codice su altri provider DBMS (che potrebbero richiedere Prepare) senza logica condizionale per determinare se Prepare debba essere chiamato o meno.

Prepare non ha alcun valore con il provider .NET per SQL Server oltre a questi casi d'uso, AFAIK.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.