Stored procedure vs. inline SQL


27

So che le procedure memorizzate sono più efficienti attraverso il percorso di esecuzione (rispetto al sql inline nelle applicazioni). Tuttavia, quando premuto, non sono super conoscibile sul perché.

Mi piacerebbe conoscere il ragionamento tecnico per questo (in modo da poterlo spiegare a qualcuno in seguito).

Qualcuno può aiutarmi a formulare una buona risposta?


1
Una query correttamente parametrizzata è valida quanto una procedura memorizzata, dal punto di vista delle prestazioni. Entrambi vengono compilati prima del primo utilizzo, entrambi riutilizzeranno il piano di esecuzione memorizzato nella cache sulle esecuzioni successive, entrambi i piani verranno archiviati nella stessa cache del piano ed entrambi verranno gestiti con lo stesso nome. Oggi non sono più previsti vantaggi prestazionali per una procedura memorizzata in SQL Server.
marc_s,

@marc_s è vero se le query sono identiche. Tuttavia, come ho sottolineato nella mia risposta, ci sono alcune caratteristiche delle query ad hoc che possono essere problemi di prestazioni anche per query che sembrano identiche.
Aaron Bertrand

Risposte:


42

Credo che questo sentimento fosse vero ad un certo punto, ma non nelle versioni attuali di SQL Server. L'intero problema era che ai vecchi tempi le istruzioni SQL ad hoc non potevano essere adeguatamente ottimizzate perché SQL Server poteva solo ottimizzare / compilare a livello di batch. Ora abbiamo un'ottimizzazione a livello di istruzione, quindi una query correttamente parametrizzata proveniente da un'applicazione può sfruttare lo stesso piano di esecuzione di quella query incorporata in una procedura memorizzata.

Preferisco ancora le procedure memorizzate dal lato DBA per i seguenti motivi (e molti di essi possono avere un impatto enorme sulle prestazioni):

  • Se ho più app che riutilizzano le stesse query, una procedura memorizzata incapsula quella logica, piuttosto che sporcare più volte la stessa query ad hoc in basi di codice diverse. Le applicazioni che riutilizzano le stesse query possono anche essere soggette alla pianificazione della cache, a meno che non vengano copiate alla lettera. Anche le differenze tra maiuscole e minuscole possono portare alla memorizzazione di più versioni dello stesso piano (spreco).
  • Sono in grado di ispezionare e risolvere i problemi di una query senza avere accesso al codice sorgente dell'applicazione o eseguire costose tracce per vedere esattamente cosa sta facendo l'applicazione.
  • Posso anche controllare (e sapere in anticipo) quali query l'applicazione può eseguire, a quali tabelle può accedere e in quale contesto, ecc. Se gli sviluppatori scrivono query ad hoc nella loro applicazione, dovranno vieni a rimorchiarmi la manica della camicia ogni volta che hanno bisogno di accedere a un tavolo che non sapevo o non potevo prevedere, o se sono meno responsabile / entusiasta e / o attento alla sicurezza, lo promuoverò utente di dbo in modo che smettano di infastidirmi. In genere questo viene fatto quando gli sviluppatori superano in numero i DBA o i DBA sono testardi. L'ultimo punto è negativo e dobbiamo essere migliori nel fornire le domande di cui hai bisogno.
  • In una nota correlata, una serie di procedure memorizzate è un modo molto semplice di inventario esattamente quali query potrebbero essere in esecuzione sul mio sistema. Non appena un'applicazione può bypassare le procedure e inviare le proprie query ad hoc, per trovarle, devo eseguire una traccia che copra un intero ciclo aziendale o analizzare tutto il codice dell'applicazione (di nuovo, che Potrei non avere accesso a) per trovare qualcosa che assomigli a una query. Essere in grado di vedere l'elenco delle procedure memorizzate (e grep una singola fonte sys.sql_modules, per riferimenti a oggetti specifici) rende la vita di tutti molto più semplice.
  • Posso fare di tutto per prevenire l'iniezione di SQL; anche se prendo input ed eseguo con SQL dinamico, posso controllare molto di ciò che può accadere. Non ho alcun controllo su ciò che uno sviluppatore sta facendo durante la costruzione di istruzioni SQL incorporate.
  • Posso ottimizzare la query (o le query) senza avere accesso al codice sorgente dell'applicazione, la possibilità di apportare modifiche, la conoscenza del linguaggio dell'applicazione per farlo in modo efficace, l'autorità (non importa la seccatura) di ricompilare e ridistribuire l'app, ecc. Ciò è particolarmente problematico se l'app è distribuita.
  • Posso forzare alcune opzioni impostate all'interno della procedura memorizzata per evitare che singole query siano soggette ad alcune delle Slow nell'applicazione, veloci in SSMS? i problemi. Ciò significa che per due diverse applicazioni che chiamano una query ad hoc, uno potrebbe avere SET ANSI_WARNINGS ONe l'altro potrebbe avere SET ANSI_WARNINGS OFFe ognuno di loro avrebbe la propria copia del piano. Il piano che ottengono dipende dai parametri in uso, dalle statistiche in atto, ecc. La prima volta che viene chiamata la query in ciascun caso, il che può portare a piani diversi e quindi prestazioni molto diverse.
  • Posso controllare cose come i tipi di dati e il modo in cui i parametri vengono utilizzati, a differenza di alcuni ORM - alcune versioni precedenti di cose come EF parametrizzerebbero una query in base alla lunghezza di un parametro, quindi se avessi un parametro N'Smith 'e un altro N' Johnson "Vorrei ottenere due diverse versioni del piano. Lo hanno risolto. Lo hanno risolto, ma cos'altro è ancora rotto?
  • Posso fare cose che gli ORM e altri framework e librerie "utili" non sono ancora in grado di supportare.

Detto questo, è probabile che questa domanda susciti più argomenti religiosi rispetto al dibattito tecnico. Se vediamo che ciò accadrà probabilmente lo spegneremo.


2
Un altro motivo per le stored procedure? Per query lunghe e complicate, devi inviare la query al server ogni volta, a meno che non sia uno sproc, quindi stai semplicemente spingendo "exec sprocname" e alcuni parametri. Ciò potrebbe fare la differenza su una rete lenta (o occupata).
David Crowell,

0

Mentre rispetto il mittente, umilmente non sono d'accordo con la risposta fornita e non per "motivi religiosi". In altre parole, credo che Microsoft non abbia fornito alcuna possibilità che riduca la necessità di una guida per l'uso delle procedure memorizzate.

Qualsiasi guida fornita a uno sviluppatore che favorisce l'uso di query SQL con testo non elaborato deve essere riempita con molti avvertimenti, in modo tale che penso che il consiglio più prudente sia quello di incoraggiare notevolmente l'uso delle Stored procedure e di scoraggiare i team di sviluppatori dall'impegnarsi nella pratica dell'incorporamento di istruzioni SQL nel codice o l'invio di richieste SQL basate su testo non elaborate e semplici, al di fuori degli SPROC SQL (procedure memorizzate).

Penso che la semplice risposta alla domanda sul perché usare uno SPROC sia come supponeva il mittente: gli SPROC vengono analizzati, ottimizzati e compilati. Pertanto, i loro piani di query / esecuzione vengono memorizzati nella cache perché hai salvato una rappresentazione statica di una query e, di solito, la varierai solo in base a parametri, il che non è vero nel caso di istruzioni SQL copiate / incollate che probabilmente si trasformano da pagina a pagina e componente / livello e sono spesso variati nella misura in cui è possibile specificare da chiamata a chiamata tabelle diverse, anche nomi di database. Consentire questo tipo di dinamica ad hocL'invio di SQL riduce notevolmente la probabilità che DB Engine riutilizzi il piano di query per le istruzioni ad hoc, secondo alcune regole molto rigide. Qui sto facendo la distinzione tra query dinamiche ad hoc (nello spirito della domanda sollevata) rispetto all'uso dell'efficiente System SPROC sp_executesql.

Più specificamente, ci sono i seguenti componenti:

  • Piani di query seriali e paralleli che non contengono il contesto dell'utente e consentono il riutilizzo da parte del motore DB.
  • Contesto di esecuzione che consente il riutilizzo di un piano di query da parte di un nuovo utente con parametri di dati diversi.
  • Cache di procedura che è ciò che il motore DB richiede per creare l'efficienza che cerchiamo.

Quando un'istruzione SQL viene emessa da una pagina Web, definita "istruzione ad hoc", il motore cerca un piano di esecuzione esistente per gestire la richiesta. Poiché si tratta di un testo inviato da un utente, verrà importato, analizzato, compilato ed eseguito, se valido. Al momento riceverà un costo di query pari a zero. Il costo della query viene utilizzato quando il motore DB utilizza il suo algoritmo per determinare quali piani di esecuzione eliminare dalla cache.

Le query ad hoc ricevono un valore di costo della query originale pari a zero, per impostazione predefinita. Alla successiva esecuzione dello stesso identico testo di query ad hoc, mediante un altro processo utente (o lo stesso), il costo della query corrente viene reimpostato sul costo di compilazione originale. Poiché il nostro costo di compilazione di query ad hoc è zero, ciò non è di buon auspicio per la possibilità di riutilizzo. Ovviamente, zero è il numero intero meno valutato, ma perché dovrebbe essere sfrattato?

Quando sorgono pressioni di memoria, e lo faranno se si dispone di un sito di uso frequente, il motore DB utilizza un algoritmo di cleanup per determinare come può recuperare la memoria utilizzata dalla cache delle procedure. Utilizza il costo della query corrente per decidere quali piani eliminare. Come puoi immaginare, i piani con un costo pari a zero sono i primi ad essere sfrattati dalla cache perché zero significa essenzialmente "nessun utente corrente o riferimenti a questo piano".

  • Nota: piani di esecuzione ad hoc: il costo corrente viene aumentato da ogni processo utente, dal costo di compilazione originale del piano. Tuttavia, il costo massimo di un piano non può essere superiore al costo di compilazione originale ... nel caso di query ad hoc ... zero. Quindi, sarà "aumentato" di quel valore ... zero - il che significa essenzialmente che rimarrà il piano di costo più basso.

Pertanto, è molto probabile che un tale piano venga sfrattato per primo quando sorgono pressioni di memoria.

Pertanto, se si dispone di un server con molta memoria "oltre le proprie esigenze", è possibile che non si verifichi questo problema con la stessa frequenza di un server occupato che ha solo memoria "sufficiente" per gestire il proprio carico di lavoro. (Siamo spiacenti, la capacità e l'utilizzo della memoria del server sono in qualche modo soggettivi / relativi, sebbene l'algoritmo non lo sia.)

Ora, se in realtà non sono corretto su uno o più punti, sono certamente aperto alla correzione.

Infine, l'autore ha scritto:

"Ora abbiamo un'ottimizzazione a livello di istruzione, quindi una query correttamente parametrizzata proveniente da un'applicazione può sfruttare lo stesso piano di esecuzione di quella query incorporata in una procedura memorizzata."

Credo che l'autore si riferisca all'opzione "ottimizza per carichi di lavoro ad hoc".

In tal caso, questa opzione consente un processo in due passaggi che evita di inviare immediatamente l'intero piano di query alla cache delle procedure. Invia solo uno stub di query più piccolo lì. Se una chiamata di query esatta viene rinviata al server mentre lo stub di query è ancora nella cache delle procedure, il piano di esecuzione dell'intera query viene salvato nella cache delle procedure, in quel momento. Ciò consente di risparmiare memoria, che durante gli incidenti di pressione della memoria, può consentire all'algoritmo di sfratto di sfrattare lo stub meno frequentemente di un piano di query più grande che è stato memorizzato nella cache. Ancora una volta, questo dipende dalla memoria e dall'utilizzo del server.

Tuttavia, devi attivare questa opzione, poiché è disattivata per impostazione predefinita.

Infine, voglio sottolineare che, spesso, il motivo per cui gli sviluppatori incorporano SQL in pagine, componenti e altri luoghi è perché desiderano essere flessibili e inviare query SQL dinamiche al motore di database. Pertanto, in un caso d'uso reale, l'invio dello stesso testo, call-over-call, è improbabile che si verifichino così come lo sono la cache / l'efficienza che cerchiamo, quando si inviano query ad hoc a SQL Server.

Per ulteriori informazioni, consultare:

https://technet.microsoft.com/en-us/library/ms181055(v=sql.105).aspx
http://sqlmag.com/database-performance-tuning/don-t-fear-dynamic-sql

Meglio,
Henry


4
Ho letto attentamente diversi paragrafi del tuo post, due o tre volte, e non ho ancora idea di quali pensieri stai tentando di trasmettere. In alcuni casi alla fine delle frasi sembra che tu stia dicendo l'esatto contrario di ciò che la frase ha iniziato tentando di dire. Hai davvero bisogno di rileggere e modificare attentamente questo invio.
Pieter Geerkens,

Grazie per il feedback Pieter. Se questo è il caso, è possibile che dovrei abbreviare le mie frasi per chiarire il punto. Potete per favore fornire un esempio di dove appaia affermare l'opposto del pensiero originale? Molto apprezzato.
Henry,

No, non intendevo Ottimizzare per i carichi di lavoro ad hoc, intendevo l'ottimizzazione a livello di istruzione. In SQL Server 2000, ad esempio, una procedura memorizzata verrebbe compilata nel suo insieme, quindi l'app non poteva in alcun modo riutilizzare un piano per la propria query ad hoc che corrispondeva a qualcosa nella procedura. Dirò che sono d'accordo con Pieter - molte delle cose che dici sono difficili da seguire. Cose come "Credo che non vi sia alcuna struttura fornita da Microsoft che riduca la necessità di una guida per l'uso delle procedure memorizzate". sono inutilmente complessi e richiedono troppe analisi per capire. A PARER MIO.
Aaron Bertrand

1
sembra che la tua avversione a "ad hoc" sql sia basata sull'idea che sql stia cambiando in qualche modo tra le esecuzioni ... questo è del tutto falso quando è coinvolta la parametrizzazione.
b_levitt

0

TLDR: non vi è alcuna differenza di prestazioni apprezzabile tra i due purché il parametro sql inline sia parametrizzato.

Questi sono i motivi per cui ho gradualmente eliminato le stored procedure:

  • Eseguiamo un ambiente applicativo "beta", un ambiente parallelo alla produzione che condivide il database di produzione. Poiché il codice db si trova a livello di applicazione e le modifiche alla struttura del db sono rare, possiamo consentire alle persone di confermare nuove funzionalità oltre il controllo qualità e di eseguire distribuzioni al di fuori della finestra di distribuzione della produzione, fornendo comunque funzionalità di produzione e correzioni non critiche. Ciò non sarebbe possibile se metà del codice dell'applicazione fosse nel DB.

  • Pratichiamo devops a livello di database (polpo + dacpacs). Tuttavia, sebbene sia possibile eliminare e sostituire il livello aziendale e le versioni successive e ripristinare solo il contrario, ciò non è vero per le modifiche incrementali e potenzialmente distruttive che devono andare ai database. Di conseguenza, preferiamo mantenere le nostre implementazioni di DB più leggere e meno frequenti.

  • Per evitare copie quasi esatte dello stesso codice per parametri opzionali, useremo spesso un modello 'dove @var è null o @ var = table.field'. Con un proc memorizzato, è probabile che tu ottenga lo stesso piano di esecuzione, nonostante intenzioni piuttosto diverse, e quindi si verifichino problemi di prestazioni o eliminando i piani memorizzati nella cache con suggerimenti di "ricompilazione". Tuttavia, con un semplice bit di codice che aggiunge un commento "firma" alla fine di sql, possiamo forzare diversi piani in base a quali variabili erano nulle (da non interpretare come un piano diverso per tutte le combinazioni di variabili - solo null vs non nullo).

  • Posso apportare cambiamenti drastici ai risultati con solo piccole modifiche al volo su sql. Ad esempio, posso avere un'istruzione che si chiude con due CTE, "Raw" e "ReportReady". Non c'è nulla che dica che devono essere utilizzati entrambi i CTE La mia istruzione sql può essere:

    ...

    seleziona * da {(formato)} "

Ciò mi consente di utilizzare lo stesso identico metodo di logica aziendale sia per una chiamata API semplificata che per un report che deve essere più dettagliato, assicurando di non duplicare la logica complicata.

  • quando hai una regola "solo procs", finisci con una tonnellata di ridondanza nella stragrande maggioranza del tuo sql che finisce per essere CRUD - Associ tutti i parametri, elenchi tutti quei parametri nella firma proc, (e ora sei in un file diverso in un progetto diverso), associ quei semplici parametri alle loro colonne. Questo crea un'esperienza di sviluppo piuttosto disgiunta.

Esistono validi motivi per utilizzare procs:

  • Sicurezza: qui hai un altro livello in cui l'app deve passare. Se l'account del servizio dell'applicazione non è autorizzato a toccare le tabelle, ma ha solo l'autorizzazione "esegui" su proc, hai una protezione aggiuntiva. Questo non lo rende un dato dato che ha un costo, ma è una possibilità.

  • Riutilizzo - Anche se direi che il riutilizzo dovrebbe avvenire in gran parte a livello aziendale per essere sicuri di non eludere le regole aziendali non correlate al db, abbiamo ancora il tipo di proc e funzioni di utilità di basso livello "usato ovunque" occasionali.

Ci sono alcuni argomenti che non supportano realmente i proc o che sono facilmente mitigabili dall'IMO:

  • Riutilizzo - L'ho menzionato sopra come un "plus", ma volevo anche menzionarlo qui che il riutilizzo dovrebbe avvenire in gran parte a livello aziendale. Un proc per inserire un record non dovrebbe essere considerato "riutilizzabile" quando il livello aziendale potrebbe anche controllare altri servizi non db.

  • Gonfiamento del piano di cache: l'unico modo per creare questo problema è se si stanno concatenando valori anziché parametrizzare. Il fatto che raramente ricevi più di un piano per proc, spesso ti fa male quando hai un 'o' in una query

  • Dimensione delle istruzioni: un ulteriore kb di istruzioni sql rispetto al nome proc sarà in genere trascurabile rispetto ai dati che ritornano. Se va bene per le Entità, va bene per me.

  • Visualizzazione della query esatta - Rendere le query facili da trovare nel codice è semplice come aggiungere la posizione della chiamata come commento al codice. Rendere il codice copiabile dal codice c # a ssms è facile come l'interpolazione creativa e l'utilizzo dei commenti:

        //Usage /*{SSMSOnly_}*/Pure Sql To run in SSMS/*{_SSMSOnly}*/
        const string SSMSOnly_ = "*//*<SSMSOnly>/*";
        const string _SSMSOnly = "*/</SSMSOnly>";
        //Usage /*{NetOnly_}{InterpolationVariable}{_NetOnly}*/
        const string NetOnly_ = "*/";
        const string _NetOnly = "/*";
  • Sql Injection: parametrizza le tue query. Fatto. Questo può effettivamente essere annullato se proc utilizza invece sql dinamico.

  • Bypassing deploy: pratichiamo anche devops a livello di database, quindi questa non è un'opzione per noi.

  • "Lento nell'applicazione, veloce in SSMS" - Questo è un problema di memorizzazione nella cache del piano che interessa entrambe le parti. Le opzioni impostate semplicemente causano la compilazione di un nuovo piano che sembra risolvere il problema per le variabili di THE ONE SET OFF. Questo risponde solo al motivo per cui si vedono risultati diversi: le stesse opzioni impostate NON risolvono il problema dello sniffing dei parametri.

  • I piani di esecuzione sql inline non vengono memorizzati nella cache - Semplicemente falso. Un'istruzione con parametri, proprio come il nome proc viene rapidamente hash e quindi un piano viene cercato da quell'hash. È uguale al 100%.

  • Per essere chiari, sto parlando di codice SQL inline non generato non generato da un ORM - usiamo solo Dapper che è al massimo un micro ORM.

https://weblogs.asp.net/fbouma/38178

/programming//a/15277/852208

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.